Switch
An accessible toggle switch for boolean settings. Built on Radix UI.
Default
Sizes
Checked
Disabled
Theming
A theme sets the default size's track and knob through the switch component
tokens. knob-inset places the knob that far from the track's outer edge on
every side and sizes it from the track height. See
Component tokens.
components:
switch:
track-width: "30px"
track-height: "17px"
knob-inset: "3px"The track's edge comes from the shared control tokens, so edges: off removes
it with every other control's edge.
The knob and the hovered track have colour keys of their own. Each falls back to the token it has always read, so a theme that sets none renders exactly as before.
| Key | Token | Falls back to |
|---|---|---|
knob-bg | --switch-knob-bg | var(--surface-page, #ffffff) |
knob-bg-checked | --switch-knob-bg-checked | var(--surface-page, #ffffff) |
track-hover-bg | --switch-track-hover-bg | var(--border-strong, #d1d5db) |
track-hover-bg-checked | --switch-track-hover-bg-checked | var(--border-strong, #d1d5db) |
A dark, borderless theme needs them. Its page colour is black, so the unchecked
knob is a black disc on a dark track, below the 3:1 that WCAG 1.4.11 asks of a
control. Its --border-strong is transparent, so the track disappears under the
pointer. Bind the knob and the hover fills in dark mode:
edges: "off"
components:
switch:
track-bg:
dark: "#383838"
knob-bg:
dark: "#d4d4d4"
track-hover-bg:
dark: "#4a4a4a"
track-hover-bg-checked:
dark: "var(--interactive-primary-bg)"knob-bg-checked does not follow knob-bg: a light unchecked knob and the
default knob on a bright checked track can sit side by side.
Installation
npx visor add switchThis copies two files into your project:
components/ui/switch/switch.tsx— the componentcomponents/ui/switch/switch.module.css— the styles
Usage
import { Switch } from '@/components/ui/switch/switch';
import { Label } from '@/components/ui/label/label';
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<Switch id="airplane" />
<Label htmlFor="airplane">Airplane mode</Label>
</div>API Reference
SwitchProps
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | — | Controlled checked state. |
defaultChecked | boolean | — | Default checked state for uncontrolled usage. |
onCheckedChange | (checked: boolean) => void | — | Callback when the checked state changes. |
size | 'default' | 'sm' | 'default' | Size of the switch. |
disabled | boolean | false | Disables the switch. |
className | string | — | Additional CSS class names to merge onto the element. |
The component also accepts all standard <button> HTML attributes (Radix renders switch as a button).
Accessibility
- Built on
@radix-ui/react-switchwith full WAI-ARIA switch semantics - Renders with
role="switch"andaria-checkedreflecting the current state - Always pair with a
<Label>using matchingid/htmlFor— the label text communicates what the switch controls - Keyboard: Space or Enter toggles the switch; Tab moves focus to/from it
- Use past-tense or noun labels ("Notifications", "Dark mode") rather than action labels ("Enable notifications")