An on or off setting that takes effect as soon as it is flipped, with a label, hint and Changed chip.
Basic
Pass a label, and a hint if the label cannot say it all. A press anywhere on the row, label included, flips the switch.
Example
Example
'use client';
import { Switch } from '@snapshot-ui/react';
import type { ReactElement } from 'react';
export default function SwitchBasic(): ReactElement {
return (
<Switch description="A short email when someone replies to your comment." defaultSelected>
Notify me about replies
</Switch>
);
}States
- On: an ink track with a canvas thumb.
- Off: the control boundary color with a raised thumb.
- Disabled: recessed, filled when on and hollow when off, so the two never look alike. The label is taupe, as on a disabled checkbox or radio.
- Read-only: looks enabled but cannot be changed.
Example
Example
'use client';
import { Switch } from '@snapshot-ui/react';
import type { ReactElement } from 'react';
export default function SwitchStates(): ReactElement {
return (
<div className="stack" style={{ width: '100%', maxWidth: '24rem' }}>
<Switch defaultSelected>On</Switch>
<Switch>Off</Switch>
<Switch isChanged>Changed</Switch>
<Switch isDisabled defaultSelected>
Disabled and on
</Switch>
<Switch isDisabled>Disabled and off</Switch>
<Switch isReadOnly defaultSelected>
Read-only
</Switch>
</div>
);
}Unsaved changes
Pass isChanged while the setting differs from the saved one. A clay chip shows beside the label, a light signal orange ring goes around the track, and a screen reader reads the chip with the switch. Flip a switch below to see them.
Example
Example
'use client';
import { Button, Switch } from '@snapshot-ui/react';
import { useState, type ReactElement } from 'react';
const SAVED = { updates: true, digest: false, alerts: true };
type Setting = keyof typeof SAVED;
const ROWS: { key: Setting; label: string; description: string }[] = [
{ key: 'updates', label: 'Product updates', description: 'A short note when something ships.' },
{ key: 'digest', label: 'Weekly digest', description: 'Every Tuesday morning.' },
{ key: 'alerts', label: 'Security alerts', description: 'Always on for owners.' },
];
export default function SwitchSettings(): ReactElement {
const [saved, setSaved] = useState(SAVED);
const [draft, setDraft] = useState(SAVED);
const isDirty = ROWS.some(({ key }) => draft[key] !== saved[key]);
return (
<div className="stack" style={{ width: '100%', maxWidth: '24rem' }}>
{ROWS.map(({ key, label, description }) => (
<Switch
key={key}
description={description}
isSelected={draft[key]}
onChange={(isSelected) => {
setDraft({ ...draft, [key]: isSelected });
}}
isChanged={draft[key] !== saved[key]}
>
{label}
</Switch>
))}
<div className="row">
<Button
isDisabled={!isDirty}
onPress={() => {
setSaved(draft);
}}
>
Save changes
</Button>
<Button
variant="tertiary"
isDisabled={!isDirty}
onPress={() => {
setDraft(saved);
}}
>
Discard
</Button>
</div>
</div>
);
}Usage
A switch works controlled or uncontrolled, and onChange receives a boolean. With a name, it submits in a plain form. Like a checkbox, it sends its value, or on by default, only while it is on. The types require an accessible name: the switch's text, an aria-label or an aria-labelledby.
import { useState } from 'react';
import { Switch } from '@snapshot-ui/react';
export function DigestSetting({ saved }: { saved: boolean }) {
const [isOn, setOn] = useState(saved);
return (
<Switch
name="digest"
value="weekly"
description="Every Tuesday morning."
isSelected={isOn}
onChange={setOn}
isChanged={isOn !== saved}
>
Weekly digest
</Switch>
);
}A switch has no required or invalid state. For a choice that a form confirms later, such as agreeing to terms, use a checkbox.
Props
Every React Aria SwitchField prop is accepted except isRequired, isInvalid and validate. These are the ones Snapshot UI adds or shapes.
changedLabel string | The text of the Changed chip. Default: 'Changed' |
|---|---|
children ReactNode | The label, beside the track. Required unless the switch has an |
defaultSelected boolean | Whether the element should be selected (uncontrolled). |
description ReactNode | A hint under the label. It becomes the switch's accessible description. |
isChanged boolean | Whether the setting differs from the saved one. Shows the clay Changed chip beside the label, which a screen reader reads with the switch, and a light signal orange ring around the track. Without a visible label, only the ring shows. |
isDisabled boolean | Whether the input is disabled. |
isReadOnly boolean | Whether the input can be selected but not changed by the user. |
isSelected boolean | Whether the element should be selected (controlled). |
name string | The name of the input element, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname). |
onChange ((isSelected: boolean) => void) | Handler that is called when the element's selection state changes. |
value string | The value of the input element, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefvalue). |
Accessibility
A native checkbox with the switch role, so a screen reader says the label, then on or off. The label names it, and the hint and the Changed chip describe it. The track and thumb are decoration. The row is a 40px target, though the track is drawn 30px tall.
| Space | Turns the switch on or off. |
|---|---|
| Tab | Moves focus to the switch. The 2px ring sits outside the track. |
The thumb slides and the track fades over 160ms. Both stop under reduced motion.
A switch with no name does not compile. TypeScript's error either names only aria-labelledby or says the props are not assignable. Both mean the switch has no accessible name.
Design rules
What keeps switches in the system.
Label what the switch controls, in sentence case, such as Weekly digest.
Show the chip and the ring together, and only while the setting is unsaved.
Let the switch act at once, or mark it changed until the page saves.
Never write the label as an action, such as Turn on weekly digest. The thumb already says on or off.
Never use a switch for a choice a form confirms later.
Never use color alone to say a setting is on. The thumb moves too.