Accessibility pattern · Form inputs

Switch

A switch is a checkbox that means on or off, not ticked or unticked: role=switch on a real checkbox input keeps the label, Space and the form value, and makes screen readers say on and off. Each switch changes the page the moment it flips, and a polite status message confirms what was saved.

WCAG criteria
7
Keyboard rules
2
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Asha Rao

Available

Meera Iyer now

Are we still on for lunch at 1?

Notifications

  • Silence calls and alerts until you turn it off.

  • Show the text of new messages in alerts.

  • A short digest of your week, every Monday morning.

  • Turned off by your organisation. Ask your admin to change it.

Changes are saved as you make them.

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
Tab or ShiftTabMoves between the switches; the switch your organisation has locked is skipped.
SpaceTurns the focused switch on or off. The change applies at once and a status message confirms it.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Focus reaches the first switchDo not disturb, switch, off. Silence calls and alerts until you turn it off.
Space turns it onOn. Do not disturb turned on
Space turns Message previews offOff. Message previews turned off
Reading the locked setting in browse modeShare my location, switch, off, unavailable. Turned off by your organisation. Ask your admin to change it.

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-switch" data-ap-switch>
  <div class="ap-switch__me">
    <span class="ap-switch__avatar" aria-hidden="true">AR</span>
    <p class="ap-switch__who">Asha Rao</p>
    <p class="ap-switch__presence" data-ap-presence>
      <span class="ap-switch__presence-on" data-ap-show="available"><span class="ap-switch__dot" aria-hidden="true"></span>Available</span>
      <span class="ap-switch__presence-off" data-ap-show="busy" hidden><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M20 14.5A8 8 0 1 1 9.5 4a6.5 6.5 0 0 0 10.5 10.5Z"/></svg>Do not disturb</span>
    </p>
  </div>

  <div class="ap-switch__alert">
    <span class="ap-switch__avatar ap-switch__avatar--small" aria-hidden="true">MI</span>
    <div class="ap-switch__alert-body">
      <p class="ap-switch__alert-top"><strong>Meera Iyer</strong> <span class="ap-switch__alert-time">now</span> <span class="ap-switch__muted" data-ap-silenced hidden>Silenced</span></p>
      <p class="ap-switch__alert-text"><span data-ap-text="full">Are we still on for lunch at 1?</span><span data-ap-text="short" hidden>New message</span></p>
    </div>
  </div>

  <h3 class="ap-switch__group">Notifications</h3>
  <ul class="ap-switch__list">
    <li class="ap-switch__row">
      <div class="ap-switch__text">
        <label class="ap-switch__label" for="switch-dnd">Do not disturb</label>
        <p class="ap-switch__desc" id="switch-dnd-desc">Silence calls and alerts until you turn it off.</p>
      </div>
      <span class="ap-switch__control">
        <input class="ap-switch__input" type="checkbox" role="switch" id="switch-dnd" aria-describedby="switch-dnd-desc" data-ap-setting="dnd" />
        <span class="ap-switch__track" aria-hidden="true">
          <span class="ap-switch__thumb">
            <svg class="ap-switch__icon ap-switch__icon--on" viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg>
            <svg class="ap-switch__icon ap-switch__icon--off" viewBox="0 0 24 24" focusable="false"><path d="M7 7l10 10M17 7 7 17"/></svg>
          </span>
        </span>
        <span class="ap-switch__state" aria-hidden="true"><span class="ap-switch__state-on">On</span><span class="ap-switch__state-off">Off</span></span>
      </span>
    </li>
    <li class="ap-switch__row">
      <div class="ap-switch__text">
        <label class="ap-switch__label" for="switch-previews">Message previews</label>
        <p class="ap-switch__desc" id="switch-previews-desc">Show the text of new messages in alerts.</p>
      </div>
      <span class="ap-switch__control">
        <input class="ap-switch__input" type="checkbox" role="switch" id="switch-previews" aria-describedby="switch-previews-desc" data-ap-setting="previews" checked />
        <span class="ap-switch__track" aria-hidden="true">
          <span class="ap-switch__thumb">
            <svg class="ap-switch__icon ap-switch__icon--on" viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg>
            <svg class="ap-switch__icon ap-switch__icon--off" viewBox="0 0 24 24" focusable="false"><path d="M7 7l10 10M17 7 7 17"/></svg>
          </span>
        </span>
        <span class="ap-switch__state" aria-hidden="true"><span class="ap-switch__state-on">On</span><span class="ap-switch__state-off">Off</span></span>
      </span>
    </li>
    <li class="ap-switch__row">
      <div class="ap-switch__text">
        <label class="ap-switch__label" for="switch-digest">Weekly summary email</label>
        <p class="ap-switch__desc" id="switch-digest-desc">A short digest of your week, every Monday morning.</p>
      </div>
      <span class="ap-switch__control">
        <input class="ap-switch__input" type="checkbox" role="switch" id="switch-digest" aria-describedby="switch-digest-desc" data-ap-setting="digest" />
        <span class="ap-switch__track" aria-hidden="true">
          <span class="ap-switch__thumb">
            <svg class="ap-switch__icon ap-switch__icon--on" viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg>
            <svg class="ap-switch__icon ap-switch__icon--off" viewBox="0 0 24 24" focusable="false"><path d="M7 7l10 10M17 7 7 17"/></svg>
          </span>
        </span>
        <span class="ap-switch__state" aria-hidden="true"><span class="ap-switch__state-on">On</span><span class="ap-switch__state-off">Off</span></span>
      </span>
    </li>
    <li class="ap-switch__row ap-switch__row--locked">
      <div class="ap-switch__text">
        <label class="ap-switch__label" for="switch-location">Share my location<svg class="ap-switch__lock" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8.5 10.5V8a3.5 3.5 0 0 1 7 0v2.5"/></svg></label>
        <p class="ap-switch__desc" id="switch-location-desc">Turned off by your organisation. Ask your admin to change it.</p>
      </div>
      <span class="ap-switch__control">
        <input class="ap-switch__input" type="checkbox" role="switch" id="switch-location" aria-describedby="switch-location-desc" data-ap-setting="location" disabled />
        <span class="ap-switch__track" aria-hidden="true">
          <span class="ap-switch__thumb">
            <svg class="ap-switch__icon ap-switch__icon--on" viewBox="0 0 24 24" focusable="false"><path d="m6 12.5 4 4 8-9"/></svg>
            <svg class="ap-switch__icon ap-switch__icon--off" viewBox="0 0 24 24" focusable="false"><path d="M7 7l10 10M17 7 7 17"/></svg>
          </span>
        </span>
        <span class="ap-switch__state" aria-hidden="true"><span class="ap-switch__state-on">On</span><span class="ap-switch__state-off">Off</span></span>
      </span>
    </li>
  </ul>
  <div class="ap-switch__foot">
    <p class="ap-switch__saved">Changes are saved as you make them.</p>
    <p class="ap-switch__status" role="status"></p>
  </div>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.1 Info and Relationships Level A

    Every switch has a label element and a description tied to it with aria-describedby, so its purpose and its effect are read together.

  • 1.4.1 Use of Color Level A

    On and off differ in the thumb's position and its icon, a check or a cross, not only in the track's fill.

  • 1.4.11 Non-text Contrast Level AA

    The off track's edge and the thumb clear 3:1 against the card, and the on track's fill clears 3:1 too.

  • 2.4.7 Focus Visible Level AA

    The focused switch shows a ring around its track, drawn when the transparent input matches :focus-visible.

  • 2.5.8 Target Size (Minimum) Level AA

    The input covers a 52 by 44 pixel area, and its label is a second, larger target.

  • 4.1.2 Name, Role, Value Level A

    role=switch on a checkbox input exposes a switch whose on or off state comes from the input's checked state.

  • 4.1.3 Status Messages Level AA

    A polite status message confirms each change once it has been applied, without moving focus.

Usage

When to use it

Use it

  • Settings that take effect immediately, like turning notifications or a feature on and off.
  • A list of independent on and off preferences, each with a short explanation.

Use something else

  • A choice that only applies when a form is sent: a checkbox says that better.
  • Choosing between two named options, like monthly or yearly: use radio buttons or a segmented control.
  • A tool state in a toolbar, like Bold: use a toggle button.

Common failures

How it usually goes wrong

  • A div that looks like a switch

    A styled div with a click handler cannot be reached with Tab, ignores Space and has no state. A checkbox input with role=switch brings all three.

  • A label that changes with the state

    A switch called "Turn on alerts" that becomes "Turn off alerts" leaves people unsure whether the label is the state or the action. The label names the setting; the state is on or off.

  • On and off shown only by color

    A green or grey track is the same to many people. The thumb here moves to the other end and changes its icon, and the option above adds On and Off in words.

  • A switch that waits for a Save button

    People expect a switch to act at once. If nothing happens until a form is sent, use a checkbox instead, which promises nothing until then.

  • A disabled switch with no reason

    A greyed-out control with no explanation leaves people guessing. The locked setting here says who turned it off and what to do.

  • A tiny target

    A 36 by 20 pixel switch is hard to hit with a finger or a tremor. The input here covers 52 by 44 pixels, and the label works as well.

Notes

Building it

  • Build the switch on input type="checkbox" with role="switch": the label, Space, the checked state and form submission all keep working, and assistive technology says on and off instead of checked.
  • The input is transparent and covers the drawn track, so the visible control is also the one that takes clicks, touch and focus.
  • A disabled input leaves the Tab order. Its description stays in the page for screen reader browse mode; if people must be able to Tab to a locked setting, use aria-disabled="true" and block the change in script.
  • Safari can also draw a native switch from input type="checkbox" switch. It is not in every browser yet, so this pattern draws its own.
  • Announce the outcome, not the key press: the switch already says on or off, so the status message says what changed and that it was saved.

Sources: WAI-ARIA Authoring Practices: Switch · ARIA in HTML: role=switch on a checkbox

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction