Skip to Content
Component ExamplesToggle Controls

Overview

Toggle controls let users switch between two states — on/off, mute/unmute, dark/light. There are three distinct patterns: switches (role="switch") for settings that take immediate effect, toggle buttons (aria-pressed) for actions that can be toggled, and checkboxes for form values submitted later. Each announces differently to screen readers, and choosing the wrong pattern confuses assistive technology users about what will happen when they interact.

WCAG Criteria:

Key requirements:

  • Use role="switch" with aria-checked for settings that take immediate effect (e.g., dark mode, notifications)
  • Use aria-pressed on a <button> for toggle actions (e.g., mute, bold, favorite)
  • Use <input type="checkbox"> for form values that are submitted later
  • The label NEVER changes when state changes — aria-checked or aria-pressed communicates the state
  • Space key toggles all three; switches and toggle buttons are single Tab stops
  • Always provide visible focus indicators

Switch vs. Div Toggle

Switch vs. Div Toggle

Inaccessible
<!-- Div toggle — no role, no state, no keyboard access --> <div onclick="toggleSwitch()"> <div class="track"><div class="thumb"></div></div> <span>Dark mode</span> </div>
Live Preview
Dark mode
Accessible
<!-- Semantic switch — role, state, and keyboard built in --> <button role="switch" aria-checked="false" onclick="let on = this.getAttribute('aria-checked') === 'true'; this.setAttribute('aria-checked', String(!on))"> <span class="track"><span class="thumb"></span></span> Dark mode </button>
Live Preview

What’s wrong with the div?

  • No switch role announced — screen readers see only static text
  • No aria-checked state — screen readers have no way to know if it’s on or off
  • Not focusable with the Tab key, so keyboard users cannot reach it
  • Cannot be toggled with Space or Enter
  • The visual animation is the only indication of state — invisible to assistive technology
  • onclick on a div only works with a mouse

What the screen reader announces:

VersionAnnouncement
Inaccessible (<div>)“Dark mode” (just text, no role, no state)
Accessible (role="switch")“Dark mode, switch, off” / “Dark mode, switch, on”

Toggle Button

Toggle Button with aria-pressed

Inaccessible
<!-- Label changes on toggle — confusing for screen reader users --> <button onclick="this.textContent = this.textContent === 'Mute' ? 'Unmute' : 'Mute'"> Mute </button>
Live Preview
Accessible
<!-- Stable label + aria-pressed conveys state --> <button aria-pressed="false" onclick="let p = this.getAttribute('aria-pressed') === 'true'; this.setAttribute('aria-pressed', String(!p))"> Mute </button>
Live Preview

What’s wrong with changing the label?

  • Screen reader users hear “Mute, button” then “Unmute, button” — the label flip implies it could be a completely different button
  • There’s no programmatic state — the screen reader cannot tell the user whether mute is currently active or inactive
  • With aria-pressed, the label stays “Mute” and the state is clear: “not pressed” means mute is off, “pressed” means mute is on
  • Stable labels also help speech recognition users who need to say the button name to activate it — a changing name is a moving target

What the screen reader announces:

VersionAnnouncement
Label-swapping button”Mute, button” then “Unmute, button” (which is the current state?)
Toggle button (aria-pressed)“Mute, toggle button, not pressed” / “Mute, toggle button, pressed”

When to Use Which Pattern

PatternElementState attributeUse when
Switch<button role="switch">aria-checkedSettings that take immediate effect (dark mode, notifications, Wi-Fi)
Toggle button<button aria-pressed>aria-pressedActions that can be toggled (mute, bold, favorite, pin)
Checkbox<input type="checkbox">checkedForm values submitted later (agree to terms, opt-in preferences)

Key differences:

  • Switch = immediate effect, no submit step. Screen readers announce “switch, on/off.”
  • Toggle button = an action with two states. Screen readers announce “toggle button, pressed/not pressed.”
  • Checkbox = deferred submission as part of a form. Screen readers announce “checkbox, checked/not checked.”
  • All three toggle with the Space key. Switches and toggle buttons also toggle with Enter.
  • The label always stays the same — the state attribute alone communicates the change.

Resources