Search topics
Toggle switches that don't say whether they are on
A switch shows on and off with colour and a sliding knob, but its state is only a CSS class, so assistive technology never hears it.
The problem
Screen reader users can find the switch but can't tell whether it is on or off, or whether pressing it did anything.
An account settings page has a row of switches: email updates, text reminders, a newsletter. Each one is a button with a pill-shaped track and a round knob. Clicking it slides the knob and turns the track from grey to green. A screen reader user tabs to the first switch and hears "Email updates, button". They press Space, the knob moves, and they hear nothing new. They have no way to tell whether email updates are now on or off.
Success criterion 4.1.2 requires that the state of a user interface component can be determined by software, and that assistive technology is told when it changes. Here the state lives only in a CSS class that draws the knob on one side or the other. The button has a name and a role, but nothing in the accessibility tree says on or off, so the change is drawn but never announced.
The same failure shows up as a div with role="switch" and no aria-checked, which axe reports as a missing required attribute. It also shows up as a switch whose aria-checked is set once on page load and never updated.
Settings pages often stack several switches in a list, and people read them in order to check what is set. When the state isn't exposed, the whole list becomes a guess.
3 of 10 groups affected
- No vision (affected)
- Low vision (affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (not affected)
- Reach, strength (not affected)
- Cognitive (affected)
- Seizures (not affected)
- Without vision Screen reader users hear the switch's name and role but not its state, so they can't check a setting or confirm that a toggle worked.
- Limited vision People using magnification or a screen reader alongside low vision may not see the knob move, and get no spoken state to fall back on.
- Limited language, cognitive and learning abilities Without a clear spoken or written state, people who need confirmation can't be sure which way a setting was left.
A scanner flags role="switch" without aria-checked, but a plain button with a CSS class passes every automated check: it has a role and a name, and nothing marks it as a switch. No scanner can tell that the class means on, or that the attribute still matches after a toggle. Code assistants often fix the label and leave the state alone, or swap the label between "On" and "Off", which changes the name instead of reporting a state. Toggle the switch and listen.
Try it
Press Tab to reach the switch, then Space to toggle it.
The panel shows what a screen reader announces. Can you tell whether email updates are on?
Broken Can you fix this?
Screen reader says: (Tab to the switch)
A recreated demo. The broken version is intentionally inaccessible.
The fix
Expose the state with a checkbox, aria-checked on a switch, or aria-pressed on a button.
- 1Keep the exposed state in step with what is drawn, on every toggle.
- 2Keep the name fixed; let the state say on or off.
- 3Show on and off by position or text, not by colour alone.
<button type="button" class="switch" onclick="this.classList.toggle('is-on')"> Email updates</button>Why this fix: Use native HTML first
The button can be reached with Tab and pressed with Enter and Space, but its state is only the is-on class. A screen reader announces "Email updates, button" whether it is on or off, and says nothing when it changes.
The browser holds the state, exposes it as checked or not checked, and toggles it with Space and a click on the label. The CSS draws the switch from :checked, so the picture can't drift from what is announced. It also submits with the form, without script.
All three pass. Use the native checkbox unless you have a reason not to: the browser keeps the state for you, so no script can leave it out of step, and it works inside a form with no extra code. That is the reason to recommend it for nearly every settings switch. Choose role="switch" when the component is already a custom button, or when you want it announced as a switch rather than a checkbox, and accept that the script must update aria-checked on every change. Choose aria-pressed when the control is really a toggle button in a toolbar, not a setting with an on and off position.
A checkbox is announced as "checked" or "not checked", not "on" or "off". Most people understand that, but if you want switch wording you can add role="switch" to the checkbox itself: browsers then announce on and off and keep the native keyboard and form behaviour. Safari's switch attribute on a checkbox does the same, but only in Safari, so don't rely on it alone.
Whichever markup you use, keep these points in mind.
- Keep the name fixed. "Email updates" with a state of on is clear; a label that changes from "Turn on email updates" to "Turn off email updates" while also reporting a state contradicts itself.
- Show the state without colour alone. A knob that moves position, or an "On" and "Off" word drawn beside the track with
aria-hidden, still reads in forced colors and for people who can't tell green from grey. - Decide when the change takes effect. A switch usually applies at once; if it saves to a server, announce failures in a status region and set the state back, rather than leaving the switch showing a value that wasn't saved.
Component libraries often ship a switch with the state on a wrapper div. Check the element that takes focus, because that is the one whose state is read.
Verify the fix
5 checks.
Test with: Keyboard, Screen reader, Forced colors, Mouse
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared switch component changes.
Limits
This is a starting pattern, not a guarantee for every application. Screen readers word the switch role differently, and some older ones announce it as a checkbox or toggle button. The CSS is a sketch; check the focus indicator and contrast of the track against your own palette. Test the complete settings journey with your target assistive technology.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.