Switch
Switch — an immediate on/off control, built on <input type="checkbox" role="switch">.
Examples
<div class="field field-inline">
<input class="switch" id="demo-wifi" name="wifi" type="checkbox" role="switch" checked />
<label class="field-label" for="demo-wifi">Wi-Fi</label>
</div>
<div class="field field-inline">
<input class="switch" id="demo-sync" name="sync" type="checkbox" role="switch" />
<label class="field-label" for="demo-sync">Background sync</label>
</div>
<div class="field field-inline">
<input class="switch" id="demo-locked" name="locked" type="checkbox" role="switch" checked disabled />
<label class="field-label" for="demo-locked">Two-factor authentication — required by your organisation</label>
</div>role="switch" is on the input. CSS cannot add a role, and a switch that announces itself as a checkbox is worse than one that looks like a checkbox.
A switch takes effect the moment it moves — if the change only lands when a form is submitted, it is a checkbox and should look like one.
<div class="field field-inline">
<input class="switch switch-sm" id="size-switch-sm" type="checkbox" role="switch" checked />
<label class="field-label" for="size-switch-sm">.switch-sm — a narrower track, still 24px tall</label>
</div>
<div class="field field-inline">
<input class="switch" id="size-switch-md" type="checkbox" role="switch" checked />
<label class="field-label" for="size-switch-md">Default</label>
</div>
<div class="field field-inline">
<input class="switch switch-lg" id="size-switch-lg" type="checkbox" role="switch" checked />
<label class="field-label" for="size-switch-lg">.switch-lg</label>
</div>The small variant narrows the track and keeps its height, because 24px is the WCAG 2.2 target-size floor and a switch that shrank in both directions would fail it.
Shown side by side like this the result reads as a rendering mistake, which is the argument for using .switch-sm where it is alone in its context: a table row, a toolbar, a dense settings panel. In forced-colours mode all three states are drawn with Highlight/HighlightText, because a switch carries its state in a fill and a position and forced colours flattens the fill.
Tokens 13
Level 2, declared on .switch itself. Set any of them on that selector to restyle this component without touching the skin.
| Token | Default |
|---|---|
| --switch-bg | var(--color-surface-sunken) |
| --switch-block-size | 1.5rem |
| --switch-border-color | var(--color-border-strong) |
| --switch-border-width | var(--border-width) |
| --switch-checked-bg | var(--color-primary) |
| --switch-checked-border-color | var(--color-primary) |
| --switch-checked-thumb-bg | var(--color-on-primary) |
| --switch-inline-size | 2.75rem |
| --switch-invalid-border-color | var(--color-danger) |
| --switch-padding | 0.1875rem |
| --switch-radius | var(--radius-full) |
| --switch-thumb-bg | var(--color-border-strong) |
| --switch-thumb-radius | var(--radius-full) |
Variants and states
Variants
.switch-lg.switch-sm
State it reads
Read from the platform, never mirrored into a class that could disagree with it.
:checked:disabled:focus:hover:user-invalid
Before you ship it
What you have to do 1 requirements
- Set
role="switch". CSS cannot add a role, and a switch-looking checkbox that announces itself as a checkbox is worse than one that looks like a checkbox. Label it like any other control. If flipping it triggers work that is not instant, say so —aria-busy, or a status region — because the thumb moving is a visual claim that the change already happened.
src/css/components/switch.css · npx mostlycss add switch