Content
In-track glyphs via checkedContent / uncheckedContent.
An instant on/off toggle for a single setting.
@garn/ui/switchDefault switch.
Interactive states, toggled live.
<Switch />State
Switch with a setting label.
sm · md · lg.
The tone axis: neutral · brand · success · warning · danger.
In-track glyphs via checkedContent / uncheckedContent.
Switch in a Field — settings row + option card.
Sent when someone replies to your thread.
A Monday summary of everything you missed.
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add switchswitch@radix-ui/react-switchclass-variance-authoritylucide-reactutilsImport
import { Switch } from "@garn/ui/switch";Generated from the component's TypeScript types.
size"lg" | "md" | "sm"mdtone"brand" | "danger" | "neutral" | "success" | "warning"brandcheckedContentReact.ReactNodeloadingbooleanuncheckedContentReact.ReactNodePlus 293 inherited native <button> attributes.
Target these data-slots and remap these tokens to restyle without forking the component.
[data-slot="switch"]| Property | Token | Tier |
|---|---|---|
trackHeight | --garn-box-md | semantic · identity |
fill | --garn-brand-solidbrandable | semantic |
fillEdge | --garn-brand-strongbrandable | semantic |
fillOff | --garn-muted | semantic |
ring | --garn-ringbrandable | semantic |
Private instance vars (never externalize): --sh
Switch flips a setting that takes effect the instant it changes — notifications on/off, dark mode, an enabled feature — with no Save step. The filled track and travelled thumb make the on/off state legible at a glance. If the choice is only confirmed later on submit (a form value), use a Checkbox; if there are more than two states, use a RadioGroup or Select.
Use a switch only when the change is immediate. The mental model is a physical toggle: flipping it does the thing now. If you find yourself wanting a Save button for a screen of switches, those should probably be checkboxes instead.
Label the setting, not the state. The Label says what the switch controls ("Email notifications") — the on/off position already communicates the state, so don't append "on/off" to the text. Reserve switches for clearly binary settings.
Reach for tone only when the state carries a status. The default brand fill is right for almost every toggle. Use success/warning/danger when the ON position itself signals something (a risky feature flag, a destructive automation) — not for decoration.
Use loading for async toggles. When flipping runs a request, set loading to show the in-thumb spinner and block interaction (it sets aria-busy + disabled) until the real result lands — then reflect it, reverting if it failed. Don't leave the switch looking done while the effect is still in flight.
Pair it with a Field for anything more than a bare toggle. A description, a card surface, or a settings row is the Field's job — drop the Switch in as the Field's control so the label, htmlFor, and aria-describedby are wired for you.
switchEnforceable contracts
Give every switch an accessible label (a <label htmlFor>, or aria-label).
when no associated <label> or aria-label/aria-labelledby
Keyboard
| SpaceEnter | Toggle on / off. |
v0.3.0v0.1.0garn doesn't yet record a per-release version history. This reflects the component's current lifecycle state; full changelog entries will appear once the metadata pipeline emits a lifecycle.history field.