Switch Role Support

Whether you use a <button> or <input type="checkbox"> as the basis for your switch depends on a few factors:

  1. Use <button> if:
    • you can count on JavaScript being available, and
    • flipping the switch has an immediate effect.

    Go read Under-Engineered Toggles Too.

  2. Use <input type="checkbox"> if:
    • you want to progressively enhance the control, and
    • flipping the switch will only take effect when the user submits it.

    Go read Under-Engineered Toggles.

Those two posts show you how to accessibly style your pill-like controls. Whether you follow any of that advice, use any of those styles, or ignore it all completely is mostly irrelevant from here out. All this post cares about is what happens once you add the switch role.

Normally, both aria-checked and aria-pressed allow for a mixed value. With the switch role, however, you are restricted to aria-checked and only the values true or false. The mixed value is invalid on the switch role in the current ARIA spec and into the ARIA 1.3 Editor’s Draft.

The aria-checked attribute of a switch indicates whether the input is on (true) or off (false). The mixed value is invalid, and user agents MUST treat a mixed value as equivalent to false for this role.

Test Case

As always, the example pen is available in debug mode for you to test with your favorite assistive technology. You should do that, especially since these results will be out of date as soon as a new release of anything comes out. I also did not test with voice control, though it generally cares little for ARIA.

There are no styles other than to indicate the buttons’ states. This post only cares about how the controls are exposed to screen reader users through the browser’s accessibility tree.

See the Pen Toggles by Adrian Roselli (@aardrian) on CodePen.

Testing

None of ARC, Axe, or Wave flag a mixed value when used with a switch role. Neither does the HTML validator. See my post Beware False Negatives for why I bother to mention this.

I left the screen reader instructions for each control in the following output. It is interesting to see how the instructions change depending on control types and roles.

Chrome 94–106 with JAWS 2021–2022

Chrome exposes aria-checked="mixed" on a switch role as “true”, which is against the specification. Interaction instructions are not offered for the switches (you do not need to provide them). Generally unrelated to this post, be careful using aria-checked on a native checkbox, as Scott O’Hara notes in a Chromium bug report.

Firefox 93–105 with NVDA 2021.2–2022.3

Firefox exposes aria-checked="mixed" on a checkbox with the switch role as “mixed”, which is against the specification. Firefox does not expose the switch role, always announcing it as a checkbox.

Narrator (Windows 10–11) with Edge 94–105

Edge exposes aria-checked="mixed" on a button with the switch role as “true”. Edge exposes aria-checked="mixed" on a checkbox with the switch role as “mixed”. Both of these are against the specification.

Narrator (Windows 11) with Firefox 105

This shows how when Firefox is paired with Narrator, Narrator diverges from NVDA when announcing the mixed state for aria-checked. I gave this a test based on feedback from James Scholes on Twitter.

TalkBack 9.1–13 (Android 11–13) with Chrome 94–106

TalkBack with Chrome announces aria-checked="mixed" on a checkbox with the switch role as “partially checked”, and on a button with the switch role as “checked”, both of which are against the specification. Interestingly, TalkBack notes you can toggle all controls, unless they are a checkbox in a mixed state, then you “activate” it.

TalkBack 9.1–13 (Android 11–13) with Firefox 93–105

TalkBack with Firefox announces a standard mixed checkbox as “not checked”. It also announces all buttons with aria-pressed as “switch”, though they do not have that role, and TalkBack does not announce their value.

VoiceOver (iPadOS 14.8–15.7) with Safari 14–15.6

VoiceOver announces a standard mixed checkbox as “unchecked”, and any other control with aria-pressed="mixed" or aria-checked="mixed" as “unchecked”. VoiceOver also announces a button with aria-pressed="mixed" as a checkbox. Finally, VoiceOver announces nothing as a switch. Scott O’Hara filed a WebKit bug in early 2019.

VoiceOver (macOS 11.4–12.6) with Safari 14.1.1–16

VoiceOver announces a standard mixed checkbox as “unchecked”. Scott O’Hara filed another WebKit bug almost two weeks ago identifying it as a regression. Otherwise Safari/VO handles switch correctly, and far better than its iOS/iPadOS cousin.

Verdict

The switch role does not allow mixed states. Ensure your switch never gets set to a mixed state, otherwise, well, problems.

Switches do not always announce as switches. It may be worth keeping this in mind for documentation — both for developers and end users.

The best performers for switches are TalkBack 9.1 / Android 11 with Firefox 93 (though buggy with toggled buttons and mixed native checkboxes) and VoiceOver / macOS 11.4 with Safari 14.1.1 (though buggy with mixed native checkboxes). The worst are Firefox 93 with NVDA 2021.2 (it does not announce them as switches, and is wrong with mixed buttons) and VoiceOver / iOS 14.8 with Safari 14 (it does not announce them as switches, and is wrong with mixed buttons and checkboxes).

I am naming these because Safari is both the best (desktop) and the worst (mobile) and Firefox is also both the best (mobile) and the worst (desktop). This should drive home the point that a browser should not be expected to perform the same on mobile and desktop, even for the same version.

If you are trying to figure out which approach is right for you, well, I don’t know. I’m not your dad.

Updated Verdict, 5 October 2022

In the year since I wrote this post, there have been minor changes.

In short, things that were broken are still broken. If a switch was not announced as a switch before, it still isn’t (looking at you NVDA/Firefox and VoiceOver/iOS).

Update: 6 October 2022

I added another example which uses aria-checked on the checkbox. I held off before because the checked property (or lack thereof) should take priority, but realized it was an incomplete test without it. The native property and the aria-checked values stay in synch.

I also filed two browser issues:

Have not gone back to sort the VO/Safari/iPadOS wonkiness yet.

In the interest of spamming all the issue trackers, I also left a comment on Core AAM #75 Sanity-check mappings for switch role.

No comments? Be the first!

Leave a Comment or Response

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>