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

PropTypeDefaultDescription
labelstringText label rendered beside the switch. Passing label or description wraps the control in a layout row.
descriptionReact.ReactNodeSecondary 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.
checkedbooleanControlled on/off state. Pair with onCheckedChange.
defaultCheckedbooleanInitial state when uncontrolled.
onCheckedChange(checked: boolean) => voidFires when the state changes. There is no onChange — the control is a button, not an input.
disabledbooleanfalseBlocks interaction and drops opacity to 50%.
requiredbooleanfalseMarks the switch as required inside a native form.
namestringName submitted with the hidden form input when rendered inside a form.
valuestring'on'Value submitted with the form when checked.
idstringautoElement id. Generated with useId when omitted, so the built-in label still binds correctly.
classNamestringAdditional Tailwind classes merged onto the track.
...restRadix Switch.Root propsEverything else is forwarded to the underlying button.

Accessibility

  • Radix renders a button with role="switch" and keeps aria-checked in sync; inside a <form> a hidden input mirrors it so name and value submit 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 no id is passed the component generates one with useId.
  • Three colour layers, three role tokens, no raw scale: the off track is bg-switch-track, the on track is bg-control-on, and the thumb is bg-control-knob. Those three are the only thing that separates the default palette from data-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.