Designer view
Switch
A binary on/off control with immediate effect — track plus thumb that slides between two positions. Uses `role="switch"` to signal "this control immediately enables/disables a behaviour" rather than "this is a boolean form field" (which is `Checkbox`). Common alternate name "Toggle" across design systems (Atlassian, Carbon, Polaris). No tri-state mixed value — switches are strictly binary.
Also called Toggle Toggle switch On/off switch
When to use
Use
For immediate-effect on/off controls — settings panes that take effect on activation, feature flags, dark-mode toggle, notification opt-in / opt-out, sound mute. Pair with a noun-shaped label ("Notifications", "Dark mode", "Save drafts") that stays constant across both states; the on/off semantic is conveyed by the switch position. Use when activation should immediately apply without form submission.
Avoid
For boolean form fields that submit with the form — that is `Checkbox`. The semantic differs: switches signal "this setting is now active"; checkboxes signal "this field has this value when the form submits". For mutually-exclusive multi-option choice (List / Grid view) — that is `SegmentedControl`. For toggle buttons in a toolbar (bold / italic / underline) — those are `Button` with `aria-pressed`, not Switch. Never use a switch when the activation needs explicit confirm-then-apply (deferred- effect contexts where a partial choice should not yet take effect).
Versus related
- checkbox
`Checkbox` is a boolean form field whose value submits with the form (only checked checkboxes submit); `Switch` is an immediate-effect control whose activation applies the behaviour right away. Checkbox supports the tri- state mixed value for parent/child propagation; Switch is strictly binary. Decision test: does the activation submit a form value (Checkbox) or immediately apply a setting (Switch)?
- segmented-control
`SegmentedControl` selects one of multiple mutually- exclusive options (List / Grid / Calendar); `Switch` is a binary on/off. SegmentedControl is multi-option mutex; Switch is binary. Use SegmentedControl when the choice has more than two values; Switch only when the answer is exactly on or off.
- button
A toggle `Button` with `aria-pressed` is a momentary action that may be in a pressed state (toolbar formatting buttons — bold / italic). `Switch` is a setting-state control that persists. Decision test: does activation perform a transient action with state memory (toggle button) or change a configuration setting that persists (Switch)?
Switch is the canonical immediate-effect-on-off primitive. Track with sliding thumb communicates "this control toggles a behaviour the moment you activate it" — settings panes, feature flags, notification preferences, dark-mode toggles. Distinct from Checkbox by the activation semantic (Switch is "do this now", Checkbox is "this field carries this value when the form submits") and by the absence of the indeterminate (mixed) state. The reference documents the four canonical slots, the noun-not- verb label rule, the form-integration bridge (no native HTML input exists for switch — components must mirror state into a hidden `<input type="checkbox">` or controlled value), and the divergence from Checkbox and SegmentedControl.
Figma anatomy
| Slot | Figma type | Hint |
|---|---|---|
root | frame | Auto-layout horizontal frame; track + thumb leading or trailing the label |
track | frame | Pill-shaped frame; background bound to data-state token (off/on) |
thumb | frame | Circle inside track frame; absolute position bound to data-state (off → start, on → end) |
input | frame | Invisible interactive layer overlapping track; carries focus ring on focus-visible |
label | text | Text element inline with track; size matches root size variant |
description | text | Smaller text below label; muted color; visibility per "has description" property |
Token usage per slot
root- spacing
- gap
spacing.tight
- gap
track- radius
- corner
radius.pill
- corner
- color
- background
color.surface.sunken
- background
thumb- radius
- corner
radius.full
- corner
- color
- background
color.surface.bg
- background
input- color
- border
color.border.focus
- border
label- color
- foreground
color.text.primary
- foreground
- typography
- size
text.sm
- size
description- color
- foreground
color.text.muted
- foreground
- typography
- size
text.xs
- size
Figma ↔ Code property map
| Figma | Kind | Code | Notes |
|---|---|---|---|
Variant | Enum | variant | standard / with-icons. with-icons adds a check / dash glyph to the thumb for redundant state signal. |
Size | Enum | size | sm / md. |
Checked | Boolean | checked | Controlled state. Strictly binary — `aria-checked="mixed"` is invalid for `role="switch"` per APG. Drives thumb position (inline-start when off, inline-end when on). |
Required | Boolean | required | Sets `aria-required="true"`. Form-bound switches require the hidden-input bridge. |
Disabled | Boolean | disabled | — |
Label | Text | children of `<label>` | Noun describing the controlled behaviour ("Notifications", "Dark mode"). Stays constant across states — on/off conveyed by thumb position plus `aria-checked`, never by label text. |
Description | Text | description | Optional supporting prose, programmatically linked via `aria-describedby`. |
Name | Text | name | Form-field key. Form participation requires a hidden `<input type="checkbox" role="switch">` bridge — `<button role="switch">` does not submit any value. |
Thumb | Slot | thumb | Decorative — slides between inline-start (off) and inline-end (on). Optional check / dash glyph in with-icons variant. |
Track | Slot | track | Decorative — colour shifts as secondary state reinforcement; thumb position is the primary signal. |
Motion
| Transition | Duration token |
|---|---|
thumbSlide | motion.duration.fast |
trackColorShift | motion.duration.fast |
Internationalisation
RTL · mirroring
Track + thumb inline order follows writing direction — track on the inline-start, label on the inline-end (LTR: track left, label right; RTL: track right, label left). Thumb position uses logical properties: `transform: translateX(0)` for off (inline-start) and `transform: translateX(<track-width>)` for on (inline- end); under RTL the translation reverses automatically via writing-direction-aware positioning. Track and thumb shapes are direction-neutral.
Text expansion
Label text expands 30-50% under translation (English "Notifications" → German "Benachrichtigungen" 70% longer; "Dark mode" → French "Mode sombre" minor expansion). Reserve no fixed width on the label slot; let it wrap to multiple lines under pressure. The track + thumb stay size-token-bound and never text-expand. Avoid embedding state words in the label — those would also need translation and break the constant-label rule.
Variants, properties, states
Variants
Structurally different versions of the component.
standard with-icons Properties
The same component, parameterised.
| Property | Type |
|---|---|
size | sm | md |
disabled | boolean |
required | boolean |
States
Browser/user-driven (interactive) vs. app-driven (data).
| Kind | States |
|---|---|
interactive | hoverfocus-visibleactivedisabled |
data | offon |
State transitions
| From | To | Trigger |
|---|---|---|
off | on | User activates the input (Space, Enter, or click on label) |
on | off | User activates the input (Space, Enter, or click on label) |
Figma↔Code mismatches
- 01 Figma
Track colour drawn as the only state signal (grey → blue)
CodeThumb position is the primary state signal; track colour is reinforcement
ConsequenceDesigners ship a switch where the only visual change between off and on is the track-background colour; developers ship a switch where the thumb also slides from inline-start to inline-end. The two surfaces visually agree in colour but designers may not draw the thumb-translation path, leaving developers without a motion specification.
CorrectDocument the thumb position as the primary state signal in the anatomy. Figma carries an off-state frame (thumb at inline-start) and an on-state frame (thumb at inline-end). Code uses `transform: translateX(...)` on state change; the track-colour shift is secondary.
- 02 Figma
Switch labelled "On / Off" or "Yes / No"
CodeLabel is a noun ("Notifications") that stays constant; on/off carried by `aria-checked`
ConsequenceDesigners label the switch with the state words ("On" / "Off" or "Enabled" / "Disabled"); developers know the APG rule says the label must stay constant. The two surfaces ship contradictory labelling intent — designers may not realise the AT contract is violated when the visible label changes per state.
CorrectDocument the label as a noun in the anatomy. Figma's label slot carries the canonical noun ("Notifications"); the on/off semantic lives in the track-and-thumb visual plus the input's `role="switch"` and `aria-checked`. Per-state label-text override is an anti-pattern.
- 03 Figma
Switch component variants for "checked"/"unchecked"/"indeterminate"
CodeSwitch has only on / off; mixed (indeterminate) is INVALID per APG
ConsequenceDesigners ship three component variants by analogy with Checkbox; developers know `aria-checked="mixed"` is not a valid value for `role="switch"`. The third variant ships as visual-only with no semantic mapping — implementers either add a fake mixed visual or drop it, leading to design / implementation drift.
CorrectSwitch is strictly binary. Document the two data states (off / on) and reject the mixed variant as a Switch anti-pattern in the anatomy. If the use case truly needs three states, the surface is a Checkbox or a RadioGroup, not a Switch.
- 04 Figma
Switch shipped without a hidden form-bridge input
CodeNative HTML has no `<input type="switch">`; bridge required for form participation
ConsequenceDesigners do not consider form submission; developers know that `<button role="switch">` does not submit any value to the form. Without a hidden `<input type="checkbox" role="switch">` mirror or a controlled bridge in JS, the switch's state is lost on form submission. Settings panes that "save on submit" lose every switch value silently.
CorrectDocument the form-bridge as canonical when the switch is form-bound. Either render `<input type="checkbox" role="switch">` (the native checkbox provides form participation, the role overrides the announcement) or mirror state into a hidden checkbox via JS plus controlled-prop bridge. Settings that take effect immediately without form submission omit the bridge.
Contracts
Non-negotiable contracts
APGAPG: Switch pattern The label is a noun describing the controlled behaviour and stays constant across both states. Implementations that ship "On" / "Off" or "Enabled" / "Disabled" labels violate the APG constant-label rule. The on/off semantic is conveyed by the track-and- thumb visual plus the input's `role="switch"` and `aria-checked`.
Without the constant noun label, users cannot identify what the switch controls. SR users hear "switch, On, on" — circular and meaningless. The label is the canonical accessible name; state words duplicate the role + `aria-checked` announcement and break the identification contract.
APGAPG: Switch role binary states Switch is strictly binary — `aria-checked` accepts only `true` and `false`. The mixed (indeterminate) value is INVALID for `role="switch"` per APG. Tri- state semantics belong to `Checkbox`.
Without this rule, implementations ship third "indeterminate" or "mixed" visuals that violate the ARIA spec. AT receives invalid attribute values and announces unpredictably (some SR fall back to "checked" silently; others read "mixed" but the role does not support it). The misuse is a category error — Switch is not Checkbox.
HTML specHTML form-submission via input checkbox Form-bound switches render a hidden `<input type="checkbox" role="switch">` (or controlled-state bridge to a native checkbox) so form submission carries the value. Implementations using `<button role="switch">` without a form bridge lose state silently on submit.
Without the bridge, switches inside `<form>` produce no submission data. Users save settings that do not persist; the bug is silent and only surfaces in downstream behaviour ("my preferences keep resetting"). Native HTML has no `<input type="switch">` so the bridge is the canonical pattern.
Vocabulary drift
- WAI-ARIA
`role="switch"` (with `aria-checked` true | false)- WAI-ARIA switch role. Native HTML has no equivalent input type — the experimental `switch` attribute on checkbox has limited browser support. The canonical web wire is `<input type="checkbox" role="switch">`.
- HTML
`<input type="checkbox" switch>` (experimental)- Experimental `switch` attribute (Safari 17.4+, partial Chrome support). Renders as a switch visual without custom CSS. Limited browser support; the role-override bridge remains canonical until adoption catches up.
- Material 3
Switch- Material 3 codifies the with-icons variant — a check glyph on the thumb when on, optional dash when off — as a first-class visual treatment. Canonical name match; the icon variant is one of the canonical variants in the UI Anatomy reference.
- Atlassian
Toggle- Atlassian uses "Toggle" — naming-only divergence from the canonical "Switch". Same role and contracts.
- Carbon
Toggle- Carbon uses "Toggle" with on/off icons baked into the track. Canonical contract is preserved; naming and with-icons variant align with Material 3.
- Polaris
Setting Toggle- Polaris combines a Switch with a heading-and-helper- text wrapper as a "Setting Toggle". The wrapper is composition; the switch primitive itself follows the canonical Switch contract.
- Apple HIG
Toggle- Apple HIG distinguishes immediate-effect from deferred- effect toggles. iOS UISwitch and SwiftUI Toggle map to the immediate-effect canonical pattern. Canonical name divergence ("Toggle"); the immediate-effect semantic is the canonical one for the Switch component.
Common mistakes
#switch-label-is-state
Label text is the on/off state instead of the controlled noun
The visible label reads "On" / "Off", "Enabled" / "Disabled", or "Yes" / "No". Users cannot identify what the switch controls; SR users hear "switch, On, on" — circular and meaningless. The APG rule that the label must stay constant is violated when the label text itself shifts with state.
Use a noun describing the controlled behaviour ("Notifications", "Dark mode", "Save drafts"). The label stays constant; on/off is conveyed by the track- and-thumb visual plus the input's `aria-checked`. SR users hear "switch, Notifications, on" — clear and identifiable.
#switch-no-form-bridge
Form-bound switch loses state on submit because no hidden input is rendered
The switch is rendered as `<button role="switch">` and placed inside a `<form>`. The form submits but the switch's state is not in the form data — `<button>` does not contribute form values. Users save settings that do not persist; the bug is silent (no error, no visual cue).
Render a hidden `<input type="checkbox" role="switch" name="<key>" value="<on-value>">` (the native checkbox provides form participation; `role="switch"` overrides the announcement) or mirror the switch state into a hidden checkbox via JS bridge. Native form submission then carries the value. Immediate-effect switches that do not bind to a form omit the bridge and apply the change directly via state-update.
#switch-color-only-state
Switch state communicated via track colour alone
The off and on states differ only in track-background colour (e.g. light grey ↔ accent blue). Colour-blind users and users in grayscale or low-light displays cannot distinguish the states. Fails WCAG 1.4.1 (Use of Colour).
The thumb position is the primary state signal — off sits at the inline-start, on sits at the inline-end. Track colour is reinforcement, not the sole signal. Test in grayscale (`filter: grayscale(1)`) — the thumb position must remain unambiguous. Optional with-icons variant adds a check / dash glyph to the thumb for additional reinforcement.
#switch-aria-checked-mixed
Switch uses `aria-checked="mixed"` for an indeterminate visual
The switch ships a third "indeterminate" or "mixed" visual for when the controlled state is loading or partially applied. The implementer sets `aria-checked="mixed"`. Per APG, `mixed` is invalid for `role="switch"` — the role accepts only `true` and `false`. AT users hear unexpected announcements; some SR fall back to "checked" silently.
Switch is strictly binary. For loading / pending states, pair the switch with a separate progress indicator or use the disabled state during the transient phase. For tri-state semantics, the canonical surface is Checkbox (which validly supports `aria-checked="mixed"`), not Switch.
#switch-target-too-small
Hit-target is below the WCAG 2.5.8 24×24 threshold
The clickable area (track plus thumb plus surrounding label region) is below 24×24 CSS pixels — common in compact settings rows where the track is rendered at 28×16 or smaller. Mobile users and motor-control- limited users struggle to activate; the visual track is misleading.
Extend the hit-target via the wrapping `<label>`. The track may stay 32×18 or 36×20 visually, but the clickable label region (track + label + adjacent padding) is ≥ 24×24. Verify with browser DevTools or automated hit-target audits. Per Apple HIG iOS, the threshold is 44×44 pt; meet the higher bar on touch-first surfaces.
Accessibility hints
| Slot | Accessibility hint | |
|---|---|---|
root | When the root is `<label>`, the input becomes the accessible name's host automatically. When the root is `<div>`, the inner `<label for="<input-id>">` provides association. Hit-target ≥ 24×24 px (WCAG 2.5.8); the wrapping label region is the canonical clickable area — clicking anywhere on the label toggles the switch. | |
track | Decorative — `aria-hidden="true"` (or rendered via SVG without a `role`). The state is announced via the input's `aria-checked` value; SR users hear "switch, on" / "switch, off", not "track at right position". Track colour is reinforcement; the thumb position is the primary state signal. | |
thumb | Decorative — `aria-hidden="true"`. Thumb position communicates state to sighted users; AT receives state via `aria-checked` on the input host. The thumb may carry an optional inline icon (check for on, X for off — Material 3 precedent) but the icon is also decorative. | |
input | `role="switch"` is mandatory — a `<button>` without the role is announced as "button" not "switch". Native HTML has no `<input type="switch">` (the experimental `switch` attribute is limited browser support); the canonical pattern is `<input type="checkbox" role="switch">` for form integration or `<button role="switch" aria-checked="true|false">` for custom-rendered switches. `aria-checked="mixed"` is INVALID for switches — the role only accepts true / false per APG. | |
label | Label text is the input's accessible name. SR users hear "switch, Notifications, on" / "switch, Notifications, off" — the noun stays constant; the state is carried by the role plus `aria-checked`. A label like "On" / "Off" or "Enabled" / "Disabled" violates the APG rule; the user cannot identify what is being toggled. | |
description | Associate via `aria-describedby` on the input. SR announces description after the label and the on/off state. Avoid using description for the on/off semantic — that lives in the role plus `aria-checked`. |