Skip to content

InputsSwitch

Switch

The on/off toggle. The label describes the destination state, not the action — 'Reduce motion' is on, not 'Enable reduce motion'.

Native <input>2 sizesDisabled state

States

Off, on, disabled. The thumb is --background on a --foreground track when on.

States

Anatomy

  • 32 × 18 px track, 9999 px radius (pill)
  • 14 × 14 px thumb, --background color, 2 px transition on translate
  • Off: --border track. On: --foreground track.
  • Focus ring is the system default applied to the input

Implementation

switch.tsx
import { forwardRef, type InputHTMLAttributes } from "react"
import { cn } from "@/lib/utils"

interface SwitchProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
  label: string
  size?: "sm" | "md"
}

export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
  function Switch({ label, size = "md", className, ...props }, ref) {
    return (
      <label className={cn("ds-switch", `ds-switch--${size}`, className)}>
        <input ref={ref} type="checkbox" role="switch" {...props} />
        <span className="ds-switch__track" aria-hidden="true">
          <span className="ds-switch__thumb" />
        </span>
        <span className="ds-switch__label">{label}</span>
      </label>
    )
  },
)

Props

PropTypeDefaultDescription
labelstringNoneRequired: the label
checkedbooleanfalseOn or off
disabledbooleanfalseDisabled state
size"sm" | "md""md"Track size

When to use a switch, when a checkbox

A switch is for binary state that takes effect immediately. A checkbox is for binary selection that takes effect on submit. "Email me a weekly digest" is a checkbox (saved with the rest of the form). "Reduce motion" is a switch (toggled, and the page re-renders).