Switch
Binary flag for display properties and settings.
Switch turns a setting on or off the moment it is flipped, like showing a column or muting a
channel. Its track fills with bg-tone when it is on and the thumb slides across, and it
comes in two sizes. It has no label of its own, so pair it with
Field and keep the label beside it.
Usage
import { Field, FieldLabel, FieldRow } from '@/components/ui/field';
import { Switch } from '@/components/ui/switch';
<Field>
<FieldRow>
<Switch id="show-assignee" checked={showAssignee} onCheckedChange={setShowAssignee} />
<FieldLabel htmlFor="show-assignee">Show assignee</FieldLabel>
</FieldRow>
</Field>onCheckedChange hands you a boolean, so it can go straight to a state setter, and
defaultChecked lets the switch keep the state instead. A plain <label htmlFor> names it
just as well when the switch is not in a Field.
On, off, and disabled
On fills the track with bg-tone, which is the primary unless you set a tone. Off fills it with bg-track, a contrast wash that reads
over any surface, and the thumb is bg-surface-raised in both. Disabled fades the track, to
the primary at 32% when it is on and the contrast ink at 16% when it is off, and turns the
thumb into the contrast ink at 64%, so a disabled switch still shows which way it is set.
Two sizes
default is a 36 by 20px track with a 16px thumb, and sm is 24 by 14px with a 12px thumb,
for a dense row or a table cell. Both take the same 44px target on a touch screen.
In a form
Inside a <form>, Switch renders a hidden native checkbox and keeps it in step, so the form
sends value under name while the switch is on and nothing while it is off. value is
"on" unless you set one, required goes on the hidden input too, and a form reset puts the
switch back where it started. A switch reads as live, though, so a reader expects it to apply
without a submit. When the change waits for a Save button, use a
Checkbox.
A development warning in forms
In development, toggling a Switch inside a <form> while a
Popover is open logs React's "flushSync was called from
inside a lifecycle method" once. Inside a form, Radix Switch keeps its hidden input in sync by
dispatching a click from an effect, and the open Popover's outside-click handler calls
flushSync on it. The Popover still closes and the Switch still toggles, and production
builds of React leave the warning out, so there is nothing to change in your code.
Props
Switch takes every prop of the Radix Switch, such as checked, defaultChecked,
onCheckedChange, name, value, required, and disabled, plus these.
| Prop | Type | Default | What it does |
|---|---|---|---|
tone | "neutral" | "primary" | "support" | "danger" | "success" | "warning" | "info" | "inherit" | "primary" | The hue of the checked track. inherit takes the tone of the surrounding scope. |
size | "default" | "sm" | "default" | A 36 by 20px track with a 16px thumb, or 24 by 14px with a 12px thumb. Both reach 44px on a touch screen, and the size lands in data-size for your own styles. |
Accessibility
- It renders a
<button type="button" role="switch">witharia-checked, so assistive tech announces a switch and whether it is on. - Space and Enter both flip it, as on any button.
- The name comes from the
FieldLabelwhosehtmlFormatches the switch'sid. A switch with no visible label needs anaria-label. requiredsetsaria-required, andaria-invalidreports the switch as invalid and turns the track's borderborder-destructive.- Focus shows a 2px ring on
:focus-visibleonly, offset from the track, in the contrast ink rather than the brand color. - In forced colors the track gets a
ButtonTextborder. Off, the thumb isButtonText, and on, the track fills withHighlightunder aHighlightTextthumb. Disabled, the border and an off thumb turnGrayText, and an on track fills withGrayTextunder aCanvasthumb, so a disabled switch still shows which way it is set. Focus keeps a transparent outline that the system paints. - The track is 36 by 20px, or 24 by 14px at
sm. On a coarse pointer, such as a touch screen, an invisible 44px square centered on it takes the tap too, and the label is a target as well. With a mouse the extra area is off. disabledsets the nativedisabledattribute, so the switch leaves the tab order, is reported as unavailable, and theFieldLabelbeside it dims.- Under reduced motion the thumb jumps across instead of sliding, and the track changes color without a transition.
When to use something else
- Checkbox for a yes or no answer that a form submits with its other fields, or for picking several items from a list.
- Radio group when the setting has a few values rather than on and off, like a row density, and Select when it has a long list of them.
- Toggle for a toolbar button that stays pressed, like bold, which has no label beside it and no form value.
Install
shadcn add @gesso/switch