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.

Examples