Who it affects
Someone booking cinema tickets with a screen reader reaches a styled switch and hears switch, off. It is the toggle for wheelchair-accessible seating, but nothing says so, and the next switch is for a hearing loop.
Someone using voice control cannot say click accessible seating because the switch has no name, and has to resort to a numbered overlay to reach it.
Someone with a cognitive disability sees three identical pills in a row with text somewhere nearby and cannot be sure which pill belongs to which option.
Broken versus fixed
Both examples are live and rendered in a sandboxed frame. The broken one is deliberately broken.
Tab to the first pill with a screen reader on: switch, on. The text beside it is not connected, so the name is missing.
Announced: Wheelchair-accessible seating, switch, on. Then Hearing loop seats, switch, off.
Show the markup behind both examples
<style>body{font-family:system-ui;padding:16px;margin:0}.row{display:flex;align-items:center;gap:12px;margin-bottom:10px}.sw{width:44px;height:24px;border-radius:12px;border:0;background:#999;position:relative;cursor:pointer}.sw[aria-checked=true]{background:#2a8f4a}.sw::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:#fff}.sw[aria-checked=true]::after{left:22px}.sw:focus{outline:3px solid #1e5aa8;outline-offset:2px}</style>
<div class="row"><button class="sw" role="switch" aria-checked="true"></button><span>Wheelchair-accessible seating</span></div>
<div class="row"><button class="sw" role="switch" aria-checked="false"></button><span>Hearing loop seats</span></div>
<style>body{font-family:system-ui;padding:16px;margin:0}.row{display:flex;align-items:center;gap:12px;margin-bottom:10px}.sw{width:44px;height:24px;border-radius:12px;border:0;background:#999;position:relative;cursor:pointer}.sw[aria-checked=true]{background:#2a8f4a}.sw::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:#fff}.sw[aria-checked=true]::after{left:22px}.sw:focus{outline:3px solid #1e5aa8;outline-offset:2px}</style>
<div class="row"><button class="sw" role="switch" aria-checked="true" aria-labelledby="wc"></button><span id="wc">Wheelchair-accessible seating</span></div>
<div class="row"><button class="sw" role="switch" aria-checked="false" aria-labelledby="hl"></button><span id="hl">Hearing loop seats</span></div>
Test it by hand in two minutes
- In DevTools, search the Elements panel for role="switch", role="checkbox" and role="radio". Each match needs visible text inside it, an aria-label, or an aria-labelledby that points at text.
- Select the toggle in DevTools and open the Accessibility pane. Check the Name row is filled in and the Checked row changes when you click the toggle.
- Turn on VoiceOver (Cmd+F5) or NVDA and Tab to each toggle. You should hear the name, the role and the state, in that order, for example Hearing loop seats, switch, off.
- Press Space on the toggle and listen. The state should be announced as changed, and the name should stay the same.
The fix
<button role="switch" aria-checked="false"></button>
<span>Hearing loop seats</span>
<button role="switch" aria-checked="false" aria-labelledby="hl"></button>
<span id="hl">Hearing loop seats</span>
Point the toggle at the visible text so both agree. If the text is inside the button already, it counts as the name and nothing else is needed.
Text inside the toggle
<div role="checkbox" aria-checked="false" tabindex="0"></div>
Remember me
<div role="checkbox" aria-checked="false" tabindex="0">Remember me</div>
Putting the text inside the element names it automatically and makes the text clickable.
Icon-only toggle
<button role="switch" aria-checked="false" class="bell"></button>
<button role="switch" aria-checked="false" class="bell" aria-label="Email reminders"></button>
Name the thing being switched, not the state. The state is announced separately from aria-checked, so do not write aria-label="Reminders off".
Use a native checkbox
<div role="checkbox" aria-checked="false" tabindex="0"></div>
<label>
<input type="checkbox" name="remember"> Remember me
</label>
A real checkbox can be styled to look like a switch and handles name, state and keyboard for free.
Common mistakes
- Placing the text next to the toggle in a span with nothing linking them.
- Writing the state into the name (aria-label="Notifications on"), so it is wrong half the time.
- Using a label element with a for attribute pointing at a div or button; label only works on form controls.
- Naming all the toggles the same thing, such as aria-label="toggle".
- Forgetting aria-checked, so the toggle has a name but never announces whether it is on.
WCAG criteria this rule maps to
axe-core tags this rule as WCAG 2.2 level A. It is one of the checks for:
- 4.1.2 Name, Role, Value (level A). Official W3C text.
The scanner can see a toggle role with no text content, aria-label, aria-labelledby or title. It cannot tell whether the name makes sense, or whether the state is actually updated when the toggle is pressed. Automated tools find only part of the accessibility problems on a page; a pass on this rule is not a pass on the criterion.
Related rules
aria-input-field-name
When a div or span is given a form role such as textbox, combobox, listbox, slider or spinbutton, assistive technology treats it as a field.
A
Form fields must have labels
label
Every input and textarea needs a label that is programmatically tied to it, not just sitting nearby.
A
Required ARIA attributes must be provided for the role
aria-required-attr
Some ARIA roles only make sense with a state or value attached.
A
ARIA attributes must have valid values
aria-valid-attr-value
Each ARIA attribute accepts a particular kind of value: true or false, a number, a word from a short list, or the id of another element.