Accessible Styled Form Controls

Switch Component: Checkbox


Last updated:

Pattern to modify a checkbox into a switch component.

Pattern Demo

Version: [type="checkbox"] styled to look like a switch

Version: [type="checkbox"][role="switch"]

Version: [type="checkbox"][role="switch"] - Alternate Markup/Style

Visual design note

-moz-appearance: none; does not completely remove the styling from the checkbox in Firefox ESR. (works as expected in Firefox 60+).

The box-shadow to provide an outline of the control does not render as expected in IE11, but works as expected in Edge.

IE and Edge don't respect the box-shadow in High Contrast mode. In this mode, the ::-ms-check is re-enabled so that people will at least see a checkmark when the switch is on, and an empty oval when off.

Pattern Details

Pattern Markup
<!-- Version: [type="checkbox"] styled to look like a switch -->
<label class="check-switch" for="switch_1">
  Example Text 1:
  <input id="switch_1" type="checkbox">
  <span aria-hidden="true"></span>

<!-- Version: [type="checkbox"][role="switch"] -->
<label class="check-switch" for="switch_2">
  Example Text 2:
  <input id="switch_2" data-check-switch="" role="switch" type="checkbox">
  <span aria-hidden="true"></span>

<!-- Version: [type="checkbox"][role="switch"] - Alternate Markup/Style -->
<div class="switchbox">
  <label for="switchbox">
    Example Text 3:
  <input id="switchbox" data-check-switch="" role="switch" type="checkbox">

Similar to the styled checkbox pattern, similarly a checkbox can be used to create a custom switch control. It should be noted that a form control with the role of switch vs a checkbox role have different expectations to screen reader users.

Even if visually styled to resemble a switch, if a native checkbox is used without the role="switch", the component should continue to function like a checkbox. Meaning that the "switch" should not change the state of a component or alter a global site or application setting, until after a user submits a form the "switch" is a child of. A role="switch" should be applied to the native checkbox if activating the form control will perform an instant state change, without the need for a form submission.

If implementing visually styled switches within your project, they should be consistently implemented for each usage. Meaning that if a checkbox is visually designed to look like a switch (but continued to be announced as a checkbox), then there should be no instances where the same design is used for a role="switch". This would create confusion in why two similar looking controls are announced differently (for sighted users who also may use screen readers), or perform actions immediately or delayed until form submission.

The JavaScript

There is little JavaScript that is needed for this component, but what is there is largely for progressive enhancement purposes.

Using the data-check-switch attribute as a hook, an input with this attribute will receive a role="switch" when the script runs. Without JavaScript, it will announce itself as a checkbox.

The script also allows for the Enter key to be used to toggle the state of the switch, as checkboxes would otherwise only allow for the Space key to toggle their state.

Affects on Screen Reader Announcements?

Giving the input type="checkbox" a role="switch" announces the form control as a "switch" or "toggle button" to screen readers that support the role, and indicate the element's state as either "on/checked" or "off/not checked".

Unfortunately, there are still some gaps in support for this role.

JAWS 2019 + Firefox 65.0.1 & Chrome 72

Announces as a "switch". Each announce "on" and "off" when toggling state. Chrome alone will announce "pressed on" for the on state.

Narrator & Edge

Announces as a "toggle switch". Announces "on" and "off" when toggling state.

VoiceOver + Safari on iOS 12.2

iOS VoiceOver continues to announce role="switch" as a "checkbox". However, with the release of iOS 12.2 (Mach 2019) the announcement of state has been fixed and "switches" will announce a "checked" or "unchecked" state.

Until iOS 12.2+ has wide adoption, one should still be wary of using role="switch".

VoiceOver + Safari on iOS 11.3 (at least) through 12.1.4

iOS VoiceOver doesn't understand role="switch" and will fallback to announcing the component as a "checkbox". However, this fallback is bugged, as the state of the checkbox will not be announced to users on toggle of the component.

Narrator & NVDA 2018.4.1 + IE11

This pairing will ignore the role="switch", and continue to announce the component as a checkbox. Checkbox state will be accurately announced on toggle.

JAWS 2018 & 2019 + IE11

This pairing will announce the form control as a role="switch", but is inconsistent with announcing the state of the component. Largely only announcing state on a user first focusing the form control with by the virtual cursor navigation or tab key. If activating the form control repeatedly, focus will be lost and return to an element higher up in the DOM.

NVDA 2018.4.1 + Chrome 72

role="switch" is announced as a "toggle button." Toggling announces as "pressed checked" and "not pressed" for their respective states.

NVDA 2018.4.1 & 2019.1beta + Firefox 65.0.1

role="switch" is announced as a "toggle button." Toggling announces as "checked" and "not checked" for their respective states.

If "pressed" NVDA will announce initial state as "toggle button not pressed, checked". Toggling state will only announce "not checked" and "checked". (Open bug)

TalkBack 7.2 (on Android 8.1) + Firefox 64.0.2 & Chrome 71

Announces as a "switch." Toggling announces as "checked" and "not checked" for their respective states.

NVDA 2018.1 and 2018.2.1 notes

These versions of NVDA paired with FireFox and Chrome will announce elements with role="switch" as "not pressed" and then announce whether they are checked or not.

It doesn't matter the current state of whether the switch is checked or not, with this version of NVDA the switch would always announce "not pressed."

This issue appears to have been fixed after testing with NVDA 2018.4.1.

Usage note:

Until issues with role="switch" have been worked out, and have wide support, it may be better to use a button element, with state indicated by aria-pressed="true/false". Or stick with a input type="checkbox" if the usage of a checkbox would be an expected user experience.

It's important to remember that switches have an expectation to perform an instant update or change to a user's settings or the UI of an application. It's best to leave checkboxes visually styled to look like checkboxes when in the context of a form, where a change wouldn't be expected until form submission.

Continue Reading

For an alternate minimal pattern to create switch checkboxes, and support for intermediate state, check out Adrian Roselli's Togglish Checkboxen CodePen, and his much longer write-up Under Engineered toggles.