Accessible Styled Form Controls

Switch Component: Toggle Button

Last updated:

Pattern to create a toggle button, visually styled to resemble a switch component.

Pattern Demo

Visual design note

Switch toggle buttons are commonly represented as a visual text label and a switch UI, or as a text label with a switch UI that has its own "On" and "Off" labeling.

By default, the component outputs the unlabeled switch UI. Using the attribute data-switch-btn-labels or manually adding a class of toggle-switch--labels, the component will add "on" and "off" labels to the switch UI.

Pattern Details

Pattern Markup
<a data-switch-btn>
  Progressively Enhanced Toggle
</a>

<button type="button"
  data-switch-btn
  data-switch-btn-labels
  data-switch-btn-pressed
  disabled>
  Native Button Toggle
</button>

<!-- Markup after JS runs -->
<!-- toggle-switch--labels is the class added to the second example -->
<button type="button"
  class="toggle-switch toggle-switch--labels"
  aria-pressed="true/false">
  button text (label) here
  <span aria-hidden="true" class="toggle-switch__ui"></span>
</button>

The baseline markup for this component is simply a button or an element that can be progressively enhanced into a role=button. The different data- attributes fire off different hooks in the setup script. Ideally a toggle button would be hidden or disabled by default, and then revealed or enabled when the script runs. Doing so would ensure users without JavaScript wouldn't be able to interact with toggle buttons that didn't do anything.

The JavaScript

Largely the JavaScript for this component is in place to modify the state of the toggle button, when a user engages with it. Any additional functionality for a "switch toggle button" would be custom and unique to the interface it was used within, and thus needs to be added by the developer implementing the component.

During the setup process for a toggle button, the JavaScript will progressively enhance a button (or a non-button element) into a toggle button.

The necessary attributes will be added to the element, and a child element to serve as the switch UI will be created, if such an element does not already exist and is identified by having the data-switch-btn-ui attribute.

Affects on Screen Reader Announcements?

When a button or role="button" has an aria-pressed attribute, it will be announced by screen readers as a "toggle button". While not the same as announcing "Switch", the general concept of these two components is similar, and the aria-pressed attribute has far better support across all browsers and screen readers.

Along with the shift in announcement from button to toggle button, screen readers will also announce the current state of the element. The following definition list details the announcements for each screen reader and browser pairing:

JAWS 18 / 2018 + Internet Explorer 11

When not selected, the toggle button is announced as:

[Accessible Name] toggle button

If activated, the word "pressed" is announced after "toggle button".

JAWS 18: when actively toggling the button, JAWS will only announce the name of the key that was pressed (space or enter).

JAWS 2018: when toggling the button for the first time, JAWS will announce: [Key used to toggle button] [Pressed or Not Pressed], [Accessible Name]. Subsequent toggles will drop the announcement of the accessible name.

JAWS 18 + Firefox ESR, JAWS 2018 + Firefox ESR + 63 (nightly)

When not selected, the toggle button is announced as:

[Accessible Name] toggle button.

If activated, the word "pressed" is announced after "toggle button". When actively toggling the button, JAWS will announce the name of the key that was pressed (space or enter), "toggle button pressed/not pressed" and then the accessible name of the toggle button.

Note: for the second example, where "on" and "off" are part of the switch UI, JAWS 18 & 2018 + FireFox ESR will announce this CSS content as part of the accessible name of the toggle button, even though "on" and "off" are contained within a span aria-hidden="true". JAWS 2018 + FireFox 63 (nightly) correctly do not announce the aria-hidden content.

JAWS 18 / 2018 + Chrome (latest)

When not selected, the toggle button is announced as:

[Accessible Name] toggle button.

If activated, the word "pressed" is announced after "toggle button". When actively toggling the button, JAWS will announce the name of the key that was pressed (space or enter), "toggle button pressed/not pressed" and then the accessible name of the toggle button.

NVDA 2018.2.1 + Firefox ESR + 63 (nightly) and Chrome (latest)

When not selected, the toggle button is announced as:

[Accessible name] toggle button, not pressed.

If activated, it will instead announce as "pressed". When actively toggling the button, NVDA will announce "pressed" or "not pressed" to match the updated state of the toggle button.

VoiceOver + Safari 11.1.1 on macOS High Sierra

When not selected, the toggle button is announced as:

[Accessible Name], toggle button. To select or deselect this checkbox, press control-option-space.

If activated, the word "selected" will be announced prior to the accessible name of the toggle button. When actively toggling the button, VoiceOver will announce "selected" or "deselected" prior to "toggle button".

Note: the announcement of "checkbox" by VoiceOver is odd.

VoiceOver + Safari on iOS 11.3 & 11.4

When not selected, the toggle button is announced as:

[Accessible Name], toggle button. Not Pressed. Double tap to toggle setting.

If activated, the word "Pressed" will be announced instead of "Not Pressed". When actively toggling the button, VoiceOver will make a clicking sound effect when the state changes, and announce "checked" when aria-pressed="true". Beyond the sound effect, VoiceOver will make no announcement when the toggle button is set to aria-pressed="false".

TalkBack (Android Accessibility Suite 6.2) + Android Chrome

When not selected, the toggle button is announced as:

[Accessible Name], not checked, toggle button. Double-tap to toggle.

If activated, "not checked" will be replaced with "checked". When actively toggling the button, TalkBack will simply announce "on" or "off" correlating to the current state.

Usage note:

Using a "switch" toggle button vs a "switch" checkbox, or even a will depend on the context of which the component is used, and if toggle button vs checkbox announcements would be more appropriate.

Ideally a button or input type="checkbox" with a role="switch" would be used when creating a "switch" component. But until all popular browsers and assistive technologies correctly interpret role="switch", it would be best to rely on the consistent behavior (regardless of varied announcements) of toggle buttons.

Continue reading

For additional information about appropriate usage of switch controls I suggest reviewing Building Inclusive Toggle Buttons, by Heydon Pickering.