Switch
A switch turns one setting on or off, and the change applies at once. There is no Save step.
<tk-switch checked helper="Applies to all new uploads">Auto-enhance photos</tk-switch>
Anatomy
| Part | What it is |
|---|---|
tk-switch |
the element; one Tab stop |
| default slot | the label, before the track, required; it is the accessible name and can hold a link |
| helper | an optional hint under the label |
| track | the pill; accent when on, gray when off |
| thumb | the white pill that moves to the on or off side |
| ✓ and ✕ marks | inside the track; with the thumb position, they show the state without color |
| spinner | beside the track while loading |
The whole row, label and track, is one click target. A long label wraps, and the track stays level with its first line. In right-to-left layouts everything mirrors.
Sizes
<tk-switch>Show previews</tk-switch> <tk-switch size="small">Compact rows</tk-switch>
size |
Track | Thumb |
|---|---|---|
medium (default) |
40 × 20px | 20 × 16px |
small |
32 × 16px | 16 × 12px |
Both rows are at least 24px tall. Use small only in dense lists of settings. The label and
helper don't change size.
States
<tk-switch checked>Wi-Fi</tk-switch> <tk-switch checked disabled>Essential cookies</tk-switch> <tk-switch loading>Sync over cellular</tk-switch>
On hover, the track changes shade. Keyboard focus shows a ring around the track.
checked: the initial state. A click toggles it; read the live state from thecheckedproperty.disabled: can't be focused or changed. Use it for settings that must stay on, or rows turned off by a parent setting. A disabled track is gray whether on or off, and in dark the two are the same color, so the thumb and the ✓ or ✕ show the state.loading: the switch keeps its current state, ignores input, and stays focusable. A spinner shows beside it, and screen readers hear "Loading" if the wait passes 2 seconds.
There is no error state. If a save fails, leave the switch as it was and say what happened
elsewhere, such as in a
Saving to a server
To flip only after a save succeeds, cancel the click, show loading, and set checked
yourself:
<tk-switch id="sync">Sync over cellular</tk-switch>
<script>
const sync = document.querySelector('#sync');
sync.addEventListener('click', async (e) => {
e.preventDefault();
sync.loading = true;
const next = !sync.checked;
if (await save(next)) sync.checked = next;
sync.loading = false;
});
</script>Without a listener, the switch flips at once, which suits settings kept on the device.
Settings lists
Stack switches as your own layout; each one stands alone. Past about 5 rows, add section
headings. If one switch controls the others, set disabled on the dependent rows while it's
off. With label-hidden, the label still has to name the setting.
When to use
- Use a switch for one setting that takes effect the moment it flips.
- Use a
Checkbox when the change waits for a Save or Submit button. A switch in such a form shows a state that hasn't been saved. - Use a
Radio group or aSelect for more than two options. - Use a
Segmented control when both options need a visible label, such as units or a billing period. - Use a
Button for a change that needs confirmation or can't be undone. - Don't swap existing checkboxes for switches just for the look.
Content
- Name the setting, not the state or an action: "Auto-enhance photos", not "Enabled" or "Turn on auto-enhance". If you want a verb, you want a button.
- Phrase it so on means the named thing happens.
- Keep the label the same in both states.
- Keep labels short, in sentence case, with no end punctuation.
- Keep a helper to one short sentence, with no full stop and no links. Put explanations there, not in a tooltip.
Do / Don't
| ✅ Do | ❌ Don't |
|---|---|
| Use a switch only when the change applies at once | Put a switch in a form that waits for Save |
| Name the setting: "Auto-enhance photos" | Write the state or an action: "Enabled", "Turn on" |
Hold the flip with loading until a save succeeds |
Flip at once and flip back on failure |
| Show a failed save in your UI | Look for an error state on the switch |
| Keep changes reversible | Use a switch to delete or confirm something |
| Show a locked setting as on and disabled | Hide settings the user can't change |
Related
Checkbox : on/off in a form, applied on submit.Radio group : exactly one of several options.Segmented control : two or more labeled options that apply at once.Spinner : the indicatorloadingshows.
API
Attributes
| Attribute | Type | Default | What it does |
|---|---|---|---|
checked |
boolean | false |
initial state; the property is live |
disabled |
boolean | false |
not focusable, not changeable |
loading |
boolean | false |
holds the state, ignores input, keeps focus, shows a spinner |
loading-label |
string | Loading |
what screen readers hear after 2 seconds of loading |
size |
medium | small |
medium |
track 40 × 20 or 32 × 16px |
helper |
string | a hint under the label | |
label-hidden |
boolean | false |
hides the label on screen, keeps the accessible name |
Slots
| Slot | Content |
|---|---|
| default | the label, required; may contain links, which don't toggle the switch |
Events
change fires when the user toggles the switch with a click, Space, or Enter, not when
your code sets checked. Read the new state from checked. Call preventDefault() on
click to stop the flip. The switch isn't a form field: it has no name or value and
submits nothing.
CSS
::part(row): the label and track row, the click target.::part(control): the inner switch button: track, thumb, and marks.
Tokens
| State | Track | Thumb | Marks | Label |
|---|---|---|---|---|
| off | fill/default; hover fill/hover |
icon/accent/on-fill + shadow-1 |
✕ icon/secondary |
text/default |
| on | fill/accent; hover fill/accent/hover |
icon/accent/on-fill + shadow-1 |
✓ icon/accent/on-fill |
text/default |
| disabled, off | surface/disabled |
icon/disabled |
icon/disabled |
text/disabled |
| disabled, on | fill/disabled |
icon/disabled |
icon/disabled |
text/disabled |
The focus ring is border/accent/focus. The helper uses text/secondary; the spinner uses
icon/secondary. In high contrast, the off track gets a 1px border/default edge. Track and
thumb use radius-full. Values per theme:
Accessibility
Built in:
- A real button with
role="switch": screen readers hear "switch, on" or "off". - Tab reaches each switch;
SpaceandEntertoggle it. A click on the label toggles it too. - The state shows without color: the thumb position and the ✓ or ✕.
- The helper is read with the switch.
loadingsetsaria-busy. - The row is at least 24px tall, and the focus ring is never hidden. With reduced motion, the thumb jumps instead of sliding.
Your part:
- Apply the change as soon as the switch flips; otherwise use a checkbox.
- For server saves, flip only after the save succeeds, and show failures in your UI.
- Keep the label the same in both states.
| Key | Action |
|---|---|
Tab |
moves focus to the switch |
Space / Enter |
turns it on or off |
Contrast limits
- Off track: 1.33:1 against the page in light, 1.82:1 in dark, and 1.50:1 in high contrast. The thumb and the ✕, at least 3.45:1, carry the state.
- Disabled: on and off tracks have the same color in dark; only the thumb position and the marks tell them apart.