Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Switch

An on/off toggle for instant, self-saving binary settings — built on Base UI Switch.

Status
stable
Since
0.1.0
Accessibility pattern
APG switch

Last updated

Install

Add Switch from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/switch

Usage

import { Switch } from "@/components/ui/switch";

<Switch defaultChecked aria-label="Email notifications" />;

Pair it with Field (horizontal orientation) for an associated, clickable label:

import { Field } from "@/components/ui/field";
import { Switch } from "@/components/ui/switch";

<Field label="Email notifications" orientation="horizontal">
  <Switch defaultChecked />
</Field>;

Base UI renders the default switch root as a <span role="switch"> plus a hidden <input>. When your markup needs a sibling <label htmlFor>, render the root as a native button so the id targets the visible interactive element:

<label htmlFor="notifications-switch">Notifications</label>
<Switch
  id="notifications-switch"
  nativeButton
  render={<button type="button" />}
/>

Examples

Sizes

sm (16px), default (20px), and lg (24px) — the track and thumb scale together. The grid shows every size in both the off and on positions, since the thumb-travel geometry (the inset gap at rest vs. when checked) differs per size.

States

On, off, and disabled (in both positions). State is driven by Base UI's data-checked / data-unchecked attributes, so it works controlled (checked + onCheckedChange) or uncontrolled (defaultChecked).

Controlled

Drive the switch from state with checked + onCheckedChange. Use this when the value must stay in sync with other UI or be persisted on change.

const [enabled, setEnabled] = useState(true);

<Switch
  checked={enabled}
  onCheckedChange={setEnabled}
  aria-label="Email notifications"
/>;

Notifications are on.

Invalid

The switch itself shows no visual invalid treatment — no destructive border and no status dot. It keeps its normal appearance while aria-invalid is set (either directly on a standalone Switch, or automatically when a wrapping <Field error=…> marks the control invalid), and the Field's error copy is the affordance that explains what must be corrected.

You must enable this to continue

Playground

Try every size and the disabled state, then copy the generated JSX.

<Field label="Email notifications" orientation="horizontal">
  <Switch />
</Field>

API Reference

PropTypeDefaultDescription
renderComponentRenderFn<HTMLProps, SwitchRootState> | React.ReactElement<unknown, string | React.JSXElementConstructor<any>>Replace the rendered track element via Base UI render composition. Pass a ReactElement or a render function — Base UI merges this wrapper's className, data-slot, and state data-* onto your element, forwards the ref, and keeps the <Switch.Thumb> child. The element must support role="switch" semantics.
size"lg" | "md" | "sm"'md'Track + thumb scale. sm (16px), default (20px), lg (24px).

Data attributes and CSS variables on Switch

AttributeValues
data-sizemirrors a prop or state value
data-slot"switch" | "switch-thumb"

Accessibility

  • Renders a <span role="switch"> with a hidden <input> for form submission by default — exposes aria-checked to assistive tech. Use nativeButton render={<button />} for sibling <label htmlFor> patterns.
  • Keyboard: Tab to focus, Space / Enter to toggle.
  • :focus-visible shows a 2px ring (outline-ring) — never outline: none.
  • disabled removes it from the tab order and blocks toggling.
  • Always give the switch an accessible name — a <Field> label, or aria-label when standalone.
KeyAction
TabMove focus to the switch
SpaceToggle the switch on / off
EnterToggle the switch on / off
ContractStates tested
Behaviourdefault, checked, disabled, error, expanded, invalid, saving
Accessibilityfocus-visible, invalid, labeled, semantic-html
Visualdefault, disabled, invalid, checked, error

Do / Don't

Do
Use a Switch for instant settings, and use Field or nativeButton when pairing it with a label.
Don't
Use an htmlFor sibling label against the default span root, or use a Switch inside a form that needs a Submit step.

On this page