AccessScanRun a free scan

Guide

Building an Accessible Toggle Switch: Roles, Names, and State

A toggle switch looks trivial: a track, a sliding thumb, an on and an off. But the accessible toggle switch is one of the most commonly botched components on the web, because the visual design hides three separate decisions that screen reader and keyboard users depend on. Which role does it expose? What is its accessible name? And how does it convey state to people who cannot see the colour change?

This guide is for developers who want to ship a toggle that works with a keyboard, a screen reader, and a colour-blind user, not just one that looks right in a design mockup. We will cover the switch role, the native checkbox alternative, naming, and conveying state beyond colour, with code you can paste and adapt.

Two valid patterns: native checkbox vs. the switch role

There is no single correct markup for a toggle. There are two solid patterns, and the right one depends on what the control does when activated.

The native checkbox approach styles an <input type="checkbox"> to look like a switch. You get keyboard operability, focus management, form submission, and a clear screen reader announcement for free. The browser does the hard part:

<label class="switch"><input type="checkbox" name="notifications" checked> Email notifications</label>

Visually, you hide the default checkbox (clip it, do not use display:none, which removes it from the accessibility tree and tab order) and draw the track and thumb with CSS, often using the :checked pseudo-class to animate the thumb. NVDA announces this as "Email notifications checkbox, checked."

The role="switch" approach communicates on/off semantics more literally. A screen reader announces it as "on" or "off" rather than "checked." Build it on a real <button> so you inherit keyboard and focus behaviour, then add the role:

<button type="button" role="switch" aria-checked="true" id="darkmode">Dark mode</button>

Rule of thumb: if the toggle takes effect immediately (a settings panel, dark mode, a feature flag), role="switch" reads more naturally. If it is part of a form the user submits later, a native checkbox is usually the cleaner choice and the safer default.

Wiring up the switch role correctly

role="switch" gives you semantics but no behaviour. If you put it on a <div>, you inherit nothing and must implement everything yourself. Putting it on a <button> means the browser already handles Enter, Space, focus, and the click target. You are then responsible for exactly three things:

  • Set and maintain aria-checked. Flip it between "true" and "false" on every change, in JavaScript, the same moment you update the visual state. A switch whose visual thumb moves but whose aria-checked never changes is silent to a screen reader.
  • Give it an accessible name (covered next).
  • Make sure activation works from the keyboard. On a <button>, Space and Enter already toggle it, so you only need a click handler. On a non-button element you would have to add tabindex="0" and key handlers for both, which is exactly the work the native element saves you.

Do not reach for aria-pressed here. aria-pressed is for toggle buttons that keep role="button", and screen readers announce them as "pressed" or "not pressed." A switch should announce "on" or "off." Mixing the two confuses users who rely on the distinction. For more on choosing the right ARIA pattern, see our guide to ARIA best practices.

Giving the toggle an accessible name

A switch with no name is announced as just "on" or "off" with no indication of what it controls. Every toggle needs an accessible name, and there are three reliable ways to provide one.

  • A wrapping or associated <label> for the native checkbox pattern. The visible text becomes the accessible name automatically. This is the most robust option and the reason the checkbox pattern is so forgiving.
  • aria-labelledby pointing at the id of visible text, useful when the label sits in a separate element such as a settings row heading.
  • aria-label with a literal string, for icon-only toggles where there is no visible text to reference.

Avoid a common trap: do not let the accessible name encode the state. Naming a switch "Notifications on" means a screen reader announces "Notifications on, on" when checked and the contradictory "Notifications on, off" when unchecked. The name describes what the control governs; aria-checked describes its state. Keep them separate.

Conveying state beyond colour

The most frequent real-world failure is a toggle that signals its state only through colour, a green track for on and a grey track for off. That fails WCAG 1.4.1 Use of Colour for users with colour-vision deficiencies, and it is invisible to anyone using a screen reader if aria-checked is also missing.

Convey state through more than one channel:

  • Programmatically, via aria-checked (or the native checked property). This is what assistive technology reads. It is non-negotiable.
  • Positionally, via the thumb moving from one side of the track to the other. Position is perceivable regardless of colour.
  • Optionally, with an on/off label or an icon (a check versus a cross, for example) inside or beside the track, never as the only cue but as helpful reinforcement.

On contrast, the track, thumb, and any state icon are user-interface components, so under WCAG 1.4.11 Non-text Contrast they need at least a 3:1 contrast ratio against adjacent colours, and any visible text label needs 4.5:1. The boundary between the on and off states must itself be distinguishable, so a pale thumb on a pale track can fail even when each colour technically passes against the page background. You can check specific pairs with our contrast checker.

Finally, do not forget the focus indicator. WCAG 2.4.7 Focus Visible requires a visible focus state, and a switch styled to hide the native outline must draw its own. Keyboard users need to see which toggle they are about to flip.

Size, hit area, and quick verification

WCAG 2.2 added success criterion 2.5.8 Target Size (Minimum), which asks for an interactive target of at least 24 by 24 CSS pixels unless adequate spacing or an inline exception applies. Toggle thumbs are often small, so make the whole label or button the clickable target, not just the visible thumb, and confirm the hit area, not the graphic, meets the minimum.

Before you ship, run through a short manual pass. Automated tooling catches missing names and contrast problems, but state announcements need a human and a screen reader:

  • Tab to the toggle. Is the focus indicator clearly visible?
  • Press Space and Enter. Does the state flip, and does the announcement change between on/off (or checked/unchecked)?
  • Turn the monitor to greyscale or use a colour-blindness simulator. Can you still tell the on state from the off state?
  • Check the hit area is at least 24 by 24 CSS pixels.

For a faster first pass, you can scan your page with AccessScan to flag missing accessible names, low-contrast UI components, and other automatable issues before you do the manual review. To understand which of these criteria are legally required in the EU under the European Accessibility Act and EN 301 549, see our overview of the European Accessibility Act, which sets WCAG 2.2 Level A and AA as the baseline from 28 June 2025.

Check your site against AccessScan

See your issues ranked by impact in seconds — free.

Run a free accessibility scan

FAQ

Should I use role="switch" or a native checkbox for a toggle?

Both are valid. A native <input type="checkbox"> gives you keyboard handling, focus, and form participation for free, and screen readers announce it clearly as checked or not checked. role="switch" communicates on/off semantics more literally and is the right choice when the control takes effect immediately without a submit step (for example, a settings toggle). If you use role="switch" on a non-form element, you must wire up aria-checked, keyboard activation, and focus yourself. When in doubt, start with a styled checkbox.

What ARIA attribute conveys a toggle's on/off state?

aria-checked, set to "true" or "false". For a native checkbox you do not set it at all, the checked property handles it. For role="switch" on a div or button you must set aria-checked and keep it in sync on every change. Do not use aria-pressed for a switch; aria-pressed belongs to toggle buttons with role="button", which screen readers announce as "pressed" rather than "on".

Does an accessible toggle switch need a visible text label, not just colour?

Yes. WCAG 1.4.1 Use of Colour means state must not be signalled by colour alone, so a green-versus-grey track is not enough. Convey state through aria-checked (which screen readers announce) plus a non-colour visual cue such as the thumb position, an on/off label, or an icon. The control also needs an accessible name via a <label>, aria-label, or aria-labelledby so users know what the switch controls.

What is the minimum size for a toggle switch?

Under WCAG 2.2 success criterion 2.5.8 Target Size (Minimum), the interactive target should be at least 24 by 24 CSS pixels, unless an exception applies such as adequate spacing between targets. Most toggle switches are larger than this, but make sure the clickable area (often the <label> wrapping the input) meets the minimum, not just the small visual thumb.

More guides