Switch
A toggle switch component with blocky styling.
The Switch component is a toggle control that allows users to turn an option on or off. It features bold borders and shadows that match the Things design system. Built from scratch using React and native HTML input elements. No dependencies on any UI library.
Code
TypeScript: Copy this code into components/ui/switch.tsx:
tsx
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
export interface SwitchProps
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type"> {}
const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
({ className, checked, onChange, disabled, ...props }, ref) => {
const [isChecked, setIsChecked] = React.useState(checked ?? false)
React.useEffect(() => {
if (checked !== undefined) {
setIsChecked(checked)
}
}, [checked])
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (disabled) return
setIsChecked(e.target.checked)
onChange?.(e)
}
return (
<label
className={cn(
"relative inline-flex h-6 w-11 items-center rounded-full border-2 border-foreground transition-colors cursor-pointer neobrutalism-shadow-sm",
isChecked ? "bg-primary" : "bg-muted",
disabled && "opacity-50 cursor-not-allowed",
className
)}
>
<input
type="checkbox"
className="sr-only"
checked={isChecked}
onChange={handleChange}
disabled={disabled}
ref={ref}
{...props}
/>
<span
className={cn(
"inline-block h-4 w-4 transform rounded-sm border-2 border-foreground bg-background transition-transform neobrutalism-shadow-sm",
isChecked ? "translate-x-6" : "translate-x-1"
)}
/>
</label>
)
}
)
Switch.displayName = "Switch"
export { Switch }JavaScript: Copy this code into components/ui/switch.jsx:
jsx
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
const Switch = React.forwardRef(
({ className, checked, onChange, disabled, ...props }, ref) => {
const [isChecked, setIsChecked] = React.useState(checked ?? false)
React.useEffect(() => {
if (checked !== undefined) {
setIsChecked(checked)
}
}, [checked])
const handleChange = (e) => {
if (disabled) return
setIsChecked(e.target.checked)
onChange?.(e)
}
return (
<label
className={cn(
"relative inline-flex h-6 w-11 items-center rounded-full border-2 border-foreground transition-colors cursor-pointer neobrutalism-shadow-sm",
isChecked ? "bg-primary" : "bg-muted",
disabled && "opacity-50 cursor-not-allowed",
className
)}
>
<input
type="checkbox"
className="sr-only"
checked={isChecked}
onChange={handleChange}
disabled={disabled}
ref={ref}
{...props}
/>
<span
className={cn(
"inline-block h-4 w-4 transform rounded-sm border-2 border-foreground bg-background transition-transform neobrutalism-shadow-sm",
isChecked ? "translate-x-6" : "translate-x-1"
)}
/>
</label>
)
}
)
Switch.displayName = "Switch"
export { Switch }Usage
TypeScript:
tsx
import { Switch } from "@/components/ui/switch"
import { useState } from "react"
function MyComponent() {
const [enabled, setEnabled] = useState(false)
return (
<Switch
checked={enabled}
onChange={(e) => setEnabled(e.target.checked)}
/>
)
}JavaScript:
jsx
import { Switch } from "@/components/ui/switch"
import { useState } from "react"
function MyComponent() {
const [enabled, setEnabled] = useState(false)
return (
<Switch
checked={enabled}
onChange={(e) => setEnabled(e.target.checked)}
/>
)
}Make sure you also have the lib/utils.ts file with the cn helper function.