Switch (toggle)
Switches change a single setting immediately. Use a checkbox when people select one or more values to submit together.
The Switch wrapper renders a form-associated <a-switch> element. It works uncontrolled with defaultChecked, or controlled with checked and onStateChange.
Playground
Tone
Switch is neutral by default. Pass tone="brand" or another tone to color the control.
tone colors the whole control: the checked track, plus the unchecked track border and thumb. Six named tones are available, or pass any CSS color for a custom tone. The label and hint stay neutral.
<Switch defaultChecked tone="neutral" label="Neutral" /><Switch defaultChecked tone="brand" label="Brand" /><Switch defaultChecked tone="critical" label="Critical" /><Switch defaultChecked tone="mediumaquamarine" label="Custom color" />Selected-only tone
To apply a tone only while selected, pass toneScope="selected" together with tone, such as tone="brand". Only the checked track is tinted; the unchecked track border and thumb stay neutral.
<Switch tone="brand" toneScope="selected" label="Off stays neutral" /><Switch defaultChecked tone="critical" toneScope="selected" label="Checked track tinted" />Coloring the text
There is no text-tone prop. To tint the label, set color on the switch with a theme-aware --text-N-{tone} token; to tint the hint, target a-switch-hint. Pair either with tone to color the whole control.
/* The class is only for the demo — use your own selector. */a-switch.toned-text { color: var(--text-1-critical); }a-switch.toned-text a-switch-hint { color: var(--text-3-critical); }Size
Three sizes scale the track, thumb, and label together: small (26×16px track), medium (30×18px, the default), and large (34×20px).
<Switch defaultChecked size="small" label="Small" /><Switch defaultChecked label="Medium" /><Switch defaultChecked size="large" label="Large" />Round or square
Switches are fully rounded by default. Pass round with a number (pixels) or CSS length to set the track radius; the thumb radius is 3px smaller. Pass round={0} for square track corners.
<Switch defaultChecked round={6} label="6px round" /><Switch defaultChecked round={2} label="2px round" /><Switch defaultChecked round={0} label="Square" />Label and hint
Pass a plain label through label, or pass richer label content as children. Add hint for a secondary line beneath. labelPosition="start" places the label and hint before the control visually while source order remains stable. For a label-less switch, pass aria-label.
<Switch defaultChecked label="Email notifications" hint="Weekly digest, never marketing." /><Switch defaultChecked label="Automatic updates" labelPosition="start" />States
Switches are either unchecked or checked.
<Switch label="Unchecked" /><Switch defaultChecked label="Checked" />Disabled
The disabled attribute dims the switch and removes it from the tab order.
<Switch disabled label="Disabled" /><Switch disabled defaultChecked label="Disabled checked" />Controlled
Uncontrolled by default: pass defaultChecked and read the value from onStateChange or at form submit. Make it controlled with checked and onStateChange to drive it from your store.
const [enabled, setEnabled] = useState(false)
<Switch checked={enabled} onStateChange={(_e, { next }) => setEnabled(next)} label="Enable notifications"/>onStateChange fires before the element applies a user toggle, with (event, { next, prev }). Call event.preventDefault() to veto an uncontrolled change. A controlled switch with no onStateChange is read-only.
Events
Three callbacks, in firing order:
| Callback | When | Cancelable | Payload |
|---|---|---|---|
onStateChange(e, { next, prev }) | before the toggle applies | ✅ e.preventDefault() vetoes | next / previous boolean |
onChange(e) | after it applies | — | native change event |
onValueChange(e, attrs) | after it applies | — | { checked, name, value } |
Use onStateChange to intercept a toggle or drive a controlled switch. Use onValueChange when you only need the new form value.
<Switch name="notifications" onValueChange={(_e, { checked }) => saveNotificationPreference(checked)} label="Email notifications"/>Accessibility
The wrapper sets role="switch", and the element keeps aria-checked in sync
with the current value. Its light-DOM label supplies the accessible name and its
hint supplies the accessible description through ElementInternals; no generated
IDs are needed. For a label-less switch, pass aria-label.
Space toggles the switch. It follows the WAI-ARIA switch pattern.
Component props
| Prop | Type | Default | Description |
|---|---|---|---|
| label? | string | — | Visible, stable label for the setting. Use children for richer label content. |
| hint? | ReactNode | — | Secondary text rendered under the label and exposed as the switch's accessible description. It does not become part of the accessible name. |
| checked? | boolean | — | Controlled checked value. In controlled mode, update this in onStateChange. |
| defaultChecked? | boolean | false | Initial checked value for an uncontrolled switch. |
| disabled? | boolean | — | Disables interaction and removes the switch from the tab order. |
| round? | booleannumberstring | — | Fully round the thumb and track. Pass a number (px) or CSS length string
for a custom track radius; the thumb radius is 3px smaller. |
| name? | string | — | Form field name. A checked switch submits value under this name. |
| value? | string | "on" | Value submitted while checked. |
| tone? | neutralbrandcriticalinfosuccesswarningstring | 'neutral' | Color of the track and thumb. A tinted tone also colors the unchecked
track border and thumb; use toneScope="selected" to color only the checked track. |
| toneScope? | allselected | 'all' | Apply tone to every state, or only while checked so the unchecked track
and thumb stay neutral. |
| size? | smallmediumlarge | 'medium' | Size variant. small=26×16px, medium=30×18px, large=34×20px. |
| labelPosition? | startend | 'end' | Put the visible label before or after the control. Grid layout changes only the visual order, preserving DOM/source order for assistive technologies. |
| onStateChange? | (event, detail) => void | — | Fired before a user toggle applies. Call event.preventDefault() to veto an
uncontrolled change; controlled consumers accept by updating checked. |
| onChange? | (event) => void | — | Native post-apply change event. |
| onValueChange? | (event, attrs) => void | — | Post-apply callback with the new form-relevant value snapshot. |
Inherited props (children, className, id, slot, style, tabIndex, title)
| Prop | Type | Default | Description |
|---|---|---|---|
| children? | ReactNode | — | Child elements. When provided, replaces the component's default label/content. |
| className? | string | — | CSS class on the component's root element (merged with the component's own
classes). Use it directly for layout and positioning — grid/flex placement,
margins, alignment — rather than wrapping the component in a <div>/<span>. |
| id? | string | — | HTML id attribute. |
| slot? | string | — | Assigns the element to a named <slot> of a parent web component (e.g.
slot="header" inside a <Card>, slot="footer" inside a <Dialog>). |
| style? | CSSProperties | — | Inline styles on the component's root element. Set layout/positioning here
(or via className) directly on the component instead of adding a wrapper. |
| tabIndex? | number | — | Tab order. Set to -1 to skip the element when tabbing. |
| title? | string | — | HTML title attribute — native browser tooltip on hover. |
Plus every standard DOM event handler (onClick,
onFocus, onKeyDown, …) and any
data-* or aria-* attribute — forwarded as-is to
the underlying <a-*> element.
Web Component
Use the web component directly when you are not using React or Preact and a native control does not fit.
The focusable host carries the state. Label and hint are light-DOM children that the element exposes as its accessible name and description.
<a-switch role="switch" tabindex="0" name="updates" default-state="checked"> <a-switch-label>Email updates</a-switch-label> <a-switch-hint>Product and security notices.</a-switch-hint></a-switch> Styling
Reach for the props first: tone chooses the control color, while toneScope chooses whether it colors all states or only the checked track. tone accepts any CSS color; size scales the control, label, and hint. The focus ring is the global --focus-ring.
<Switch tone="#e0457b" defaultChecked label="Custom color" /><a-switch> is light DOM. The track is ::before, the thumb is ::after, and the label / hint are a-switch-label / a-switch-hint children. Target them with plain CSS. The classes in these examples are demo hooks. Replace them with a selector you own.
a-switch.strong-label { color: var(--text-1-brand); font-weight: 700;}
/* A CSS-only check / × in the free part of the track. */a-switch.track-marks::before { display: flex; align-items: center; font-size: 10px; font-weight: 700; line-height: 1;}a-switch.track-marks:state(checked)::before { content: "✓"; justify-content: flex-start; padding-inline-start: 4px; color: var(--switch-thumb);}a-switch.track-marks:not(:state(checked))::before { content: "×"; justify-content: flex-end; padding-inline-end: 4px; color: var(--switch-track-off-stroke);}