Toggle
A switch for one setting that takes effect the moment it moves and is undone by moving it back. If the change waits for a submit, or cannot be reversed straight away, use a checkbox or a button.
Live preview
Rendered by the component itself from the same generated tokens as the rest of the site.
<Toggle
label="Email notifications"
checked
onChange={setNotify}
/>Installation
Install it with the shadcn CLI, along with everything it uses. New project? Set it up first, as Quick start shows.
npx shadcn@latest add https://www.graphite-ui.com/r/toggle.jsonimport { Toggle } from '@/components/ui/toggle'Anatomy
Slots come from the contract, so this list and the code cannot disagree about what the component is made of.
- LabelRequiredDescribes the setting being toggled, 16 above the switch. The control renders it itself. There is no wrapper to take it from — the kit builds label text into each form control.
- ValueOptionalThe kit's Show value, on by default. The state as text (On, Off), Body/3, 8 after the switch. Decorative, since the switch announces its own state.
- Supporting textOptionalHelp text, or error text. The kit calls this Helper / Error text and builds it into the control the same way.
Variants
Default is 48 by 24 and Small 32 by 16, whose thumb carries a check when on. The label sits above and the value text beside the switch, as the kit draws it; Toggle only hides both from view and keeps the label as the switch’s name.
States
Focus is a pseudo-class in the code and a variant in the kit, so the page forces it here. The kit draws no hover state for a toggle and the code has none. Disabled keeps one track for on and off, as the kit draws it, and turns the thumb to disabled content so its position still shows which is which. Read-only drops the fill for a thin rule and keeps the thumb readable. Error is the code’s own: passing errorText puts the control in it, and the track takes a danger ring.
API reference
Generated from the contract, versioned with it, and checked by drift-check. This table cannot describe props the component does not have.
| Prop | Type | Default | Notes |
|---|---|---|---|
| checked | — | — | |
| disabled | — | — | |
| readOnly | boolean | — | The kit's Read-only. The switch stays focusable, carries aria-readonly and never reports a change. No track fill, a 1px rule inside it in the disabled tone, an on-surface thumb. |
| size | 'default' | 'sm' | — | The kit's Size. Default is 48 by 24 with an 18px thumb; Small is 32 by 16 with a 10px thumb that carries the kit's check when on. Both keep a 32px hit area. |
| hideLabel | boolean | — | The kit's Toggle only. The label is hidden from view, not from assistive tech, so the switch keeps its name; the value goes with it. |
| showValue | boolean | — | The kit's Show value. True by default, as the kit has it. |
| valueText | — | — | The value's two strings, On and Off by default. |
| label | — | — | Required. There is no shape in which this control exists unlabelled, and no wrapper left to supply one. |
| helpText | — | — | Supporting copy. Suppressed while errorText is present. |
| errorText | — | — | Its presence resolves the error state, so error text and error styling cannot be shown apart. This was Field's guarantee and it survives Field. |
Design tokens
Every swatch is live. Change the source color in the header and this table repaints, because it reads the same roles the component does.
successFill when on, as the kit binds it (D3 on #219). The thumb's position, not the colour, is what says on, so no meaning rests on colour alone.outlineFill when off.on-primaryThe thumb while enabled, the kit's icon-on-color.primaryThe focus ring, through the family's focus step. Its disabled step is the track when disabled, on or off alike as the kit draws it, and its disabled content is the thumb, the read-only rule and the dimmed text, so the thumb stays visible and its position still tells on from off.textThe value at Body/3.spacingTrack and thumb dimensions.radiusTrack corner. The thumb is a shape, not a radius step.on-surface-variantLabel and helper text, which the kit binds to onSurfaceVariant rather than onSurface.on-surfaceThe value text, the read-only thumb, and the check on the Small thumb.dangerError ring around the track, and error text. One role for both, so the two cannot drift apart.motionDuration for the track fill and the thumb travel. The easing stays a plain ease rather than the settle curve, because a switch is a short mechanical move and not something entering the viewport.
Usage
The contract's prohibitions, written as the choices you will actually face.
Do
- Name the setting (“Email notifications”) and let the position and the value text say whether it is on.
- Apply the change as soon as the switch moves. There is no Save step for a toggle.
- Use it for changes a reader can undo by flipping it back.
- Add helpText when the name alone does not say what the setting will do.
Don’t
- Label it “On” or “Off”. The value text already says that, so the label should name what is being switched.
- Use a toggle to delete, send or publish. An action that cannot be taken back at once is a Button.
- Put toggles in a form that waits for a submit button. A switch reads as done, so use a checkbox there.
- Rely on the green to say it is on. The thumb’s position says it; the colour only agrees.
Accessibility
What the component does for you, and what it leaves to you.
- Roles
- A native checkbox with
role="switch", so a screen reader announces a switch that is on or off rather than a checkbox. In error the input carriesaria-invalid; read-only carriesaria-readonlyand stays focusable, but does not change. The value text is hidden from assistive tech, because the switch already announces its state. - Keyboard
- Tab reaches it and Space flips it, as with any native checkbox. Clicking the label flips it too.
- Labels
- The label is a real
labeltied to the input. Help and error text are linked witharia-describedby, and error text is announced as it appears. - Focus
- A 2px ring in
--graphite-primary-focusaround the track, on keyboard focus only. In error it sits outside the danger ring rather than replacing it. - Motion
- The track fill and the thumb travel run on
--graphite-motion-fastand stop animating under prefers-reduced-motion. - Target
- The track is 48 by 24, or 32 by 16 at Small. The input under it is the full width and 32px tall at both sizes.
Figma parity
The kit's Toggle page ships 45 variants across 4 sets. The code exposes 10 props. This table is where those two facts are reconciled instead of quietly diverging.
| Kit axis | Values | Code | How it maps |
|---|---|---|---|
| Toggled | False · True | checked | On is success and off is outline, with an on-primary thumb, as the kit binds them. |
| Size | Default · Small | size | default and sm: 48 by 24 and 32 by 16. The Small thumb carries a 6px check when on. |
| Toggle only | False · True | hideLabel | The label is hidden from view, not from assistive tech, so the switch keeps its name. |
| Show label · Show value | True · True | hideLabel · showValue | The label above, the value (On, Off by default) 8 beside the switch. |
| State | Enabled · Focus · Disabled | disabled | Focus is :focus-visible, a pseudo-class (governance rule 7). The kit’s ring is an unbound Carbon blue; the code uses primary-focus. Disabled is one track for on and off with the thumb in disabled content; the kit binds the disabled label to the fill step, the code dims it as every form control does. |
| State | Read-only | readOnly | No fill, a 1px rule inside the track, an on-surface thumb. Focusable, never changes. |
| State | Skeleton | — | No counterpart by rule. The kit has no error state for a toggle; the code’s errorText and its danger ring are its own. |
