A two-state control that turns one setting on or off at once, such as showing vehicle trails on the map.
When to use
Use Switch for one setting that takes effect the moment it is turned on or off, such as showing vehicle trails on the map.
| Use instead | When |
|---|---|
| Checkbox | The answer is sent later, with a form's button |
| FilterChip | The thing turned on and off filters the map's vehicles, in a row of filters |
| RadioGroup | The choice is exactly one of several, such as the theme the map is drawn in |
| SegmentedControl | The choice is one of two to five ways to show one view |
| Button | The control runs an action, such as following a vehicle |
Kongens Nytorv
Metro M1, M2 and M3.
Variants and states
| Rest | Hover | Pressed | Focus | Disabled | |
|---|---|---|---|---|---|
| Default |
- Rest
- Hover
- Pressed
- Focus
- Disabled
Props
| Name | Type | Default | Description |
|---|---|---|---|
checked | boolean | — | Whether the switch is on, when the caller controls it. Announced as on or off. |
defaultChecked | boolean | false | Whether the switch is on at first, when it keeps its state itself. |
onChange | (event: Event, value: boolean, details: Switch.Root.ChangeEventDetails) => void | — | Called when a click, Space or Enter, or a click on its label, turns the switch on or off, with the new state as value; details.reason is 'none', and details.cancel() keeps the state as it was. event is details.event, the hidden input's change. |
disabled | boolean | false | Whether the switch ignores the pointer and the keys. It leaves the tab order, keeps its state, and is announced as dimmed. |
readOnly | boolean | false | Whether the switch shows its state but can't be changed. It keeps its place in the tab order and is announced as read-only. |
required | boolean | false | Whether the switch must be on before its form submits. Announced as required; the label says so to the eye. |
name | string | — | The name the switch's value is submitted under in a native <form>. Inside a Base UI Field.Root, the field's name wins. |
className | string | — | Reaches the root. className is merged after pn-Switch. |
style | CSSProperties | — | Reaches the root. className is merged after pn-Switch. |
ref | Ref<HTMLElement> | — | Reaches the root. className is merged after pn-Switch. |
slotProps | { thumb?: SwitchThumbProps | undefined; } | — | Props for each part: thumb. |
checkedboolean, default —- Whether the switch is on, when the caller controls it. Announced as on or off.
defaultCheckedboolean, defaultfalse- Whether the switch is on at first, when it keeps its state itself.
onChange(event: Event, value: boolean, details: Switch.Root.ChangeEventDetails) => void, default —- Called when a click, Space or Enter, or a click on its label, turns the switch on or off, with the new state as
value;details.reasonis'none', anddetails.cancel()keeps the state as it was.eventisdetails.event, the hidden input's change. disabledboolean, defaultfalse- Whether the switch ignores the pointer and the keys. It leaves the tab order, keeps its state, and is announced as dimmed.
readOnlyboolean, defaultfalse- Whether the switch shows its state but can't be changed. It keeps its place in the tab order and is announced as read-only.
requiredboolean, defaultfalse- Whether the switch must be on before its form submits. Announced as required; the label says so to the eye.
namestring, default —- The name the switch's value is submitted under in a native
<form>. Inside a Base UIField.Root, the field's name wins. classNamestring, default —- Reaches the root.
classNameis merged afterpn-Switch. styleCSSProperties, default —- Reaches the root.
classNameis merged afterpn-Switch. refRef<HTMLElement>, default —- Reaches the root.
classNameis merged afterpn-Switch. slotProps{ thumb?: SwitchThumbProps | undefined; }, default —- Props for each part:
thumb.
Use it
import { Switch } from '@mylder/perron'Ask your agent
get_reference({ library: "base-ui", component: "Switch" })Keyboard and ARIA
From the Base UI reference for Switch and the installed SwitchRoot and useButton. "From" says who does it.
| Key | Does | From |
|---|---|---|
Tab, Shift+Tab | Moves focus to the switch and away; a disabled switch is skipped, a read-only one is not | The browser, and Base UI's tabIndex |
Space | Turns the switch on or off on release, through the hidden input's click, and calls onChange with the new state; nothing while read-only or disabled | Base UI |
Enter | Turns the switch on or off on press, the same way; nothing while read-only or disabled | Base UI |
Switch adds no keys of its own.
| Element | Role | Accessible name from | States announced |
|---|---|---|---|
root | switch, from Base UI, on a <span> | The caller's aria-label, aria-labelledby, a wrapping <label>, a <label> whose htmlFor is its id, or a Field.Label, which Base UI turns into aria-labelledby | aria-checked, on or off; aria-disabled; aria-readonly; aria-required; aria-invalid in an invalid Field |
thumb | none: a <span> with no content | none | none |
input | none: aria-hidden="true", tabIndex −1 | none | none |
What axe won't catch, from the contract, each with how this component meets it:
- Everything the eye is told, the ear is told, for every shape each prop accepts: the thumb's place and the track's fill are
aria-checked, heard as on or off after the name; a disabled switch's tertiary colours arearia-disabled, heard as dimmed; a read-only switch isaria-readonly; an invalid one's danger edge isaria-invalid, with the Field's message as its description. - Every landmark and named region has an accessible name: none here; a switch is neither. Its own name comes from the caller, which every story gives (CR-3).
- Every default label can be overridden: none here; the switch owns no strings.
- Every shape the type accepts renders, in every variant: the switch takes no
ReactNodeprop; every combination ofchecked,disabledandreadOnlyrenders, as the stories show. - A hidden copy kept for animation is
aria-hidden, and a collapsed region isinert: the hidden input is Base UI's,aria-hiddenand out of the tab order; nothing collapses. - Native semantics first: Base UI's
role="switch"on a<span>, with a native<input type="checkbox">beside it for the label and the form; withrenderandnativeButton, a native<button>. - Focus moves in reading order and returns on close: the switch is one tab stop in document order, focus stays on it when it toggles, and a click on its label moves focus to it; it opens nothing to return from.
- Every target is at least
size.target-minsquare: the smallest target is the track itself, 40 × 24 px, and a wrapping<label>makes its text a target too. - Loops stop when the system asks for less motion: no loop; the thumb's slide and the fill's change are transitions over
duration.switch, which the tokens shorten to 0.01 ms under reduced motion (CR-6).
Owned strings
The switch shows no "On" or "Off" word, though Accessibility asks toggles to say their state in words as well as in looks. That rule asks for the state once, either in words that change on a plain button, such as "Follow" and "Following", or from the role of a control whose label stays the same; a switch's label names the setting and stays the same, Base UI's role="switch" says on or off to the ear in every locale with no word of Perron's, and the eye gets the thumb's place as well as the fill, so the state never rests on colour alone. A word beside every switch would also lengthen each settings row in Danish, and the caller can still write one in the label.
No owned strings: every word comes from the caller, as the label or aria-label.
Do and don't
For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.
| Rule | Level | Do | Don't | Checked by |
|---|---|---|---|---|
| DD-1 | must | Use a Switch for a setting that takes effect the moment it changes, such as showing vehicle trails or the stations on the map | Use it for an answer a form sends later with a button: a checkbox says "choose now, apply on submit", where a switch says "this is on now" | review |
| DD-2 | must | Name it with a visible <label> or a Field's label that names the setting, in sentence case: "Show vehicle trails" | Leave it unnamed, or name it with aria-label where there is room for a visible label: a sighted person can't tell what an unlabelled switch turns on | review |
| DD-3 | must | Keep the label the same whether the switch is on or off | Write the state in the label, "Trails on", or swap it when the switch flips: the switch already says on or off, and a changing label reads as a second state | review |
| DD-4 | should | Put the label before the switch in a settings row, and let a long label wrap | Squeeze the switch to fit a long label, or cut the label off: the track keeps its size, and a Danish label is longer than its English | review |
| DD-5 | should | Filter the map's vehicles with a FilterChip, and pick one way of showing a view with a SegmentedControl | Use a row of switches for one choice of several, or for the top bar's filters: switches each turn their own setting on, and a chip shows its count | review |
| DD-6 | must | Run an action, such as following a vehicle, with a Button | Use a switch to start something that then runs on its own: a switch holds a state, it doesn't trigger one | review |
| DD-7 | should | Say why a switch is disabled or read-only, in its label or the Field's description | Disable a switch with no word of why: a dimmed switch can't explain itself | review |