Skip to main content
Graphite UI

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.

Contract 3.0.0Kit · 4 sets · 45 variantsOpen in Figma

Live preview

Rendered by the component itself from the same generated tokens as the rest of the site.

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.json
import { 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.

A summary of new comments, once a day.
  1. 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.
  2. 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.
  3. 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.

Off
On
Size: Small
Toggle only
With help text
Supporting copy.

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.

Enabled
Focus
Disabled
Read-only
Error
Add an email address to turn this on.On keeps its fill inside the 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.

Toggle props
PropTypeDefaultNotes
checked——
disabled——
readOnlyboolean—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.
hideLabelboolean—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.
showValueboolean—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 carries aria-invalid; read-only carries aria-readonly and 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 label tied to the input. Help and error text are linked with aria-describedby, and error text is announced as it appears.
Focus
A 2px ring in --graphite-primary-focus around 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-fast and 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 axes and how they map to code
Kit axisValuesCodeHow it maps
ToggledFalse · TruecheckedOn is success and off is outline, with an on-primary thumb, as the kit binds them.
SizeDefault · Smallsizedefault and sm: 48 by 24 and 32 by 16. The Small thumb carries a 6px check when on.
Toggle onlyFalse · TruehideLabelThe label is hidden from view, not from assistive tech, so the switch keeps its name.
Show label · Show valueTrue · TruehideLabel · showValueThe label above, the value (On, Off by default) 8 beside the switch.
StateEnabled · Focus · DisableddisabledFocus 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.
StateRead-onlyreadOnlyNo fill, a 1px rule inside the track, an on-surface thumb. Focusable, never changes.
StateSkeleton—No counterpart by rule. The kit has no error state for a toggle; the code’s errorText and its danger ring are its own.