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.

VoiceOver + Safari on iOS 11.3, 11.4, & 12.0

Unfortunately mobile 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.

JAWS 2018 + 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.

Note that NVDA 2018.1 and Chrome will announce the initial state of the switch as "toggle button not pressed" and will only announce the "checked" state, making no announcement if unchecking the form control. If a switch has been set to checked, and a user navigates back to it, NVDA + Chrome and Firefox ESR will announce "toggle button not pressed, checked". This isn't necessarily bugged behavior though, as there are other examples of controls changing state in this manner.

Usage note:

Until issues with role="switch" have been worked out, 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.