Switch
An on/off toggle for instant, self-saving binary settings — two sizes, a choice-card layout, and a 24px hit area.
- Status
- Since
0.1.0- Accessibility pattern
- APG switch
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Switch before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify switchInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/switchRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
Usage
import { Switch } from "@/components/ui/switch";
<Switch />;Examples
Description
Pair a Switch with FieldLabel and FieldDescription inside a horizontal Field when the setting
needs a sentence of explanation.
Focus is shared across devices, and turns off when you leave the app.
Choice Card
Card-style selection, where FieldLabel wraps the whole Field so the entire card is clickable.
Disabled
Add disabled to the Switch, and data-disabled to the Field so the label dims with it.
Invalid
Add aria-invalid to the Switch, and data-invalid to the Field. The destructive border
holds in every state, focused or not (FOC-14).
You must accept the terms and conditions to continue.
Size
size takes sm (24×14) and default (32×18.4). Both carry the same invisible 24px hit area.
RTL
The thumb translation is written with rtl: counterparts, so a right-to-left document moves the
thumb the other way without any extra work.
Focus is shared across devices, and turns off when you leave the app.
يتم مشاركة التركيز عبر الأجهزة، ويتم إيقاف تشغيله عند مغادرة التطبيق.
States
Off, on and disabled, side by side.
Playground
Try both sizes with the disabled and invalid states, then copy the generated JSX.
<Field orientation="horizontal">
<Switch id="notifications" />
<FieldLabel htmlFor="notifications">Email notifications</FieldLabel>
</Field>API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
size | "default" | "sm" | — |
Accessibility
- Renders Base UI's Switch, which is a native
<button role="switch">carryingaria-checked; Enter and Space flip it. - The visible track is 14–18px tall, so an
::afterpseudo-element extends the pointer target to at least 24×24 (A11Y-2), verified by a realelementFromPointprobe. - On
:focus-visiblethe global 2px outline inbase.cssapplies (FOC-1). The recipe carries no ring of its own and nooutline-none(FOC-6). - A Switch has no visible text of its own — give it an
aria-label, or aFieldLabelbound withhtmlFor, as every example here does.
| Contract | States tested |
|---|---|
| Behaviour | default, checked, disabled, invalid |
| Accessibility | native-or-base-ui-semantics, browser-accessibility-test, checked, invalid, disabled |
| Visual | default, checked, invalid, disabled |
Do / Don't
Deviations
Upstream's file plus packages/ui/upstream/patches/switch.patch. Every hunk:
- FOC-1, FOC-6 — the
outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50glow is removed, along with thearia-invalidhalo rings.base.cssowns the single:focus-visibleoutline, andoutline-nonewould suppress it. - FOC-14 — the invalid tint stays upstream's
aria-invalid:border-destructive, held in every state, focused or not (FOC-14 retired FOC-5'snot-focus:guard). - FOC-12 — upstream rings the whole choice card and cancels the control's own ring with
group-has-[:focus-visible]/field-label:border-transparentand:ring-0. This system rings the control, so both counter-rules are removed with the card ring they existed to serve. - DOC-2 —
cnis imported from@vegastack/design. - A11Y-24 — the invisible hit area grows to at least 44px tall on a coarse pointer. Widened by MK, 2026-10-26.