Switch

How the Switch works with a keyboard and assistive tech, and where it stands against WCAG 2.2 AA.

FreeReact and VueSwitch blocks24 blocksPro
AAWCAG 2.2 target
8/16contrast pairs passing
4keyboard interactions

The Switch is a <button role="switch"> with aria-checked, so it is announced as a switch that is on or off and flips with Space or Enter. Because it acts at once, it should only ever control something that takes effect immediately.

Keyboard#

Tab

Moves focus to the switch. A disabled or read-only switch is skipped.

Shift+Tab

Moves focus to the previous focusable element.

Space

Turns the switch on or off.

Enter

Turns the switch on or off, as any button does.

Semantics#

<button role="switch">

Native focus and keyboard, announced as a switch rather than a button.

Always
aria-checked

true when on, false when off, so the state is read, not inferred from colour.

Always
aria-labelledby

Points at the title, so it is read as Dark mode, switch, on.

Label row and card
aria-label

A switch with no visible title needs a name passed in.

Switch alone
aria-describedby

Links the description, so it is read after the state.

description
disabled

Removed from the tab order. Read-Only uses it too.

Disabled or Read-Only
aria-hidden="true"

The cross, tick and rings are drawing only.

Icon, rings

Colour contrast#

Every variant and state has to meet WCAG 2.2 Level AA. Measure the colours as they land on screen: a translucent hover or press wash over whatever sits behind it.

3:1

Off track · Off

The #f2f2f2 track is 1.12:1 on white. An off switch is shown only by the thumb's shadow, well under 3:1.

WCAG 1.4.11 Non-text Contrast ↗
3:1

On track · On

Grey #2a2a2a and Brand #005cf8 against the page, and the white thumb against them, all pass.

WCAG 1.4.11 Non-text Contrast ↗
Not colour alone

State not by colour · All

The thumb's position shows the state as well as the colour, and aria-checked gives it to assistive tech. The optional cross and tick add one more signal.

WCAG 1.4.1 Use of Color ↗WCAG 4.1.2 Name, Role, Value ↗
4.5:1

Label and description · Label row, Card

The title in #2a2a2a and the sub-label and description in #6d6d6d pass on white and on the card's 4% hover wash.

WCAG 1.4.3 Contrast (Minimum) ↗
3:1

Focus indicator · Focus visible

A 2px ring in #1a6ef8 off a white gap, on keyboard focus only. It measures 4.54:1 on white, so it clears 3:1 on every light surface the library uses.

WCAG 1.4.11 Non-text Contrast ↗WCAG 2.4.13 Focus Appearance ↗
Not required

Disabled · Disabled

Inactive controls are exempt. The disabled text and glyphs (#cbcbcb) are deliberately faint.

WCAG 1.4.3 Contrast (Minimum) ↗

Measured contrast#

The track against a white page, and the optional cross or tick against the white thumb, from the ds.css tokens; every row is a graphic and is held to 3:1. 8 of 16 pairs fall below their minimum (4.5:1 for text, 3:1 for graphics) and are marked in amber.

Intent · partOffOff hoverOnOn hover
Grey · track on page1.12below the minimum1.25below the minimum14.35passes20.14passes
Brand · track on page1.12below the minimum1.25below the minimum5.40passes8.53passes
Grey · glyph on thumb1.62below the minimum1.62below the minimum14.35passes14.35passes
Brand · glyph on thumb1.62below the minimum1.62below the minimum5.40passes5.40passes

Motion#

  • The thumb slides over 200ms and the track changes colour over 180ms. Both are turned off under prefers-reduced-motion, and the switch changes at once.
  • While pressed on, the thumb shrinks 2px. Under reduced motion it still shrinks, without the transition.

Checklist#

Use a switch only for a setting that applies the moment it changes. If it waits for Save, use a Checkbox.

The name says what is switched (Dark mode), never the state (On); aria-checked carries the state.

The off track (#f2f2f2, 1.12:1) is under the 3:1 non-text minimum. Use the rings, or add a darker edge, until the track is darkened.

The off-state cross is #cbcbcb on white (1.62:1); it adds little. The on-state tick passes.

Small (20px) and x-Small (16px) keep a 24px hit area around the track, which meets WCAG 2.5.8.

In the label row the title is not a <label>, so clicking it does not flip the switch. Make the row a label, or keep the switch itself large enough to hit.

Read-Only is drawn with disabled and leaves the tab order. Where the value matters, also state it in text.