Components
Switch
A toggle switch for binary on/off states. Built on Radix Switch — a button that reports role="switch" — in two sizes, with an optional label and description.
Basic usage
With description
Automatically save changes every 30 seconds.
A description can hold a fragment
Since 1.8.0, description is a React.ReactNode, not a string. When it was a string, a description that needed a marker, a <strong> or a link had to be lifted out of the switch and re-aligned by hand against the control's internal measurements — copying the package's private spacing into the app, where a package upgrade would break it silently. Now the node renders as the children of the same <p>, so the alignment stays the package's job. A plain string still works exactly as before — this is a pure type widening.
Adds the experimental dashboard. Preview
Sizes
Two sizes. The thumb travel is derived from the track for each one — track − thumb − 2px — so the gap either side of the thumb stays equal whether the switch is on or off.
Controlled
Drive the switch from state with checked and onCheckedChange. The callback receives a boolean, so it can be handed a state setter directly.
Currently off.
Disabled
Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Text label rendered beside the switch. Passing label or description wraps the control in a layout row. |
description | React.ReactNode | — | Secondary helper text below the label. A ReactNode since 1.8.0, so it can carry a link, a <strong> or a status marker inline — the value renders as the children of the same <p>, so a plain string still works exactly as before. |
size | 'sm' | 'md' | 'md' | Track and thumb size — sm is 28×16px, md is 36×20px. |
checked | boolean | — | Controlled on/off state. Pair with onCheckedChange. |
defaultChecked | boolean | — | Initial state when uncontrolled. |
onCheckedChange | (checked: boolean) => void | — | Fires when the state changes. There is no onChange — the control is a button, not an input. |
disabled | boolean | false | Blocks interaction and drops opacity to 50%. |
required | boolean | false | Marks the switch as required inside a native form. |
name | string | — | Name submitted with the hidden form input when rendered inside a form. |
value | string | 'on' | Value submitted with the form when checked. |
id | string | auto | Element id. Generated with useId when omitted, so the built-in label still binds correctly. |
className | string | — | Additional Tailwind classes merged onto the track. |
...rest | Radix Switch.Root props | — | Everything else is forwarded to the underlying button. |
Accessibility
- Radix renders a button with
role="switch"and keepsaria-checkedin sync; inside a<form>a hidden input mirrors it sonameandvaluesubmit natively. - The button itself takes focus and shows the focus ring on the track. Space and Enter toggle the state.
- Label is clickable and associated via
htmlFor; when noidis passed the component generates one withuseId. - Three colour layers, three role tokens, no raw scale: the off track is
bg-switch-track, the on track isbg-control-on, and the thumb isbg-control-knob. Those three are the only thing that separates the default palette fromdata-contrast="high". - A switch is a non-text component, so WCAG 1.4.11 applies to three pairs: track against the page, thumb against the track, and on-track against off-track. On the high-contrast palette the first two clear 3:1 (3.83–12.67:1); the third stays at 1.70–2.57:1 on purpose, because the thumb's position already carries the state and no colour pair clears both tests against a white page.
- The default palette knowingly sits below 3:1 on all three pairs — it reproduces the approved Comitor look. Ship
data-contrast="high"for users who need the stronger one.