Exploring an accessible switch component

Create an accessible and progressively enhanced switch component using HTML and CSS.

Fabio Iassia
4 min readNov 20, 2021

This article is the first of a series to explore and create commonly used web components following two main goals:

Accessible: The component must comply with the Web Content Accessibility Guidelines (WCAG) recommendations, improving users' interaction regardless of the device, gadget, or technology.

Progressive enhanced: The component must provide decent usability for the core functionality on older devices and browsers before including improvements like visual style, animations, or extra features.

An extra goal is to use a minimum or no JavaScript and rely mostly on HTML and CSS properties. With the objectives defined, let's start the development of our switch component.

Semantic HTML

As a building block, you will use the input type="checkbox" since its similarity to the switch behavior, also meet the progressive enhancement goal, ensuring a functional element for older browsers like Internet Explorer 11.

<input type="checkbox" />

Now the label tag comes in handy, to present a meaningful and related text to the future switch component.

<input type="checkbox" />
<label>Feature</label>

For now, you have semantic elements, but clicking on the label does not trigger the checkbox check, to achieve the desired behavior, allowing users to click both switch and label, add the id to the input and the for on the label to associate the elements.

<input type="checkbox" id="feature" />
<label for="feature">Feature</label>

Alternatively, move the input inside the label, making an implicit association:

<label>
<input type="checkbox" />
Feature
</label>

Accessibility concerns

To achieve the accessibility goal, our switch needs to meet three requirements:

Focus and outline visibility

When users navigate using the keyboard with TAB, the switch component must present the default outline effect applied by default for browsers.

Keyboard navigation and interaction

In addition to the keyboard navigation using TAB, users must be able to interact with the switch using the space key.

Screen readers

The switch must work as well for screen readers, and using the above semantic HTML structure you don't need to include aria attributes.

The expected narration audio for the switch is: [unchecked,Feature,checkbox]

The CSS

Important note: As presented by Jason Knight in the comments, the use of pixels as units goes against a truly accessible implementation, for the sake of simplicity the code examples below will use pixels just to present the rationale behind the final implementation that can be found here (using em and variables).

Now it is time to transform the simple checkbox into our switch, to do so include a class to be our main selector:

<input type="checkbox" class="switch" />

Before starting customizing the input you need to remove the default OS style without changing the accessible context and behavior of the element:

.switch {
appearance: none;
-webkit-appearance: none;
border: none;
}
  • appearance: none remove the style for Chrome and Firefox
  • -webkit- prefix remove the styles for Safari
  • border: none remove an extra border style on iOS

With that, you have a blank checkbox, let's include the switch style:

.switch {
appearance: none;
-webkit-appearance: none;
border: none;
width: 30px;
height: 20px;
border-radius: 10px;
background-color: dodgerblue;
}

To prevent including an extra HTML tag just for the handler inside the switch, you can use the pseudo-element ::after

.switch::after {
content: "";
display: block;
background-color: white;
width: 16px;
height: 16px;
border-radius: 50%;
transform: translate(2px, 2px);
}

The transform property use hardware acceleration (GPU) where possible. So using translate() over left/top brings performance benefits to the animation.

Speaking of animation, include the style using the :checked selector, and a transition to create a smooth effect when the switch changes its state.

.switch::after {
...
transition: transform 0.1s ease-in-out;
}
.switch:checked::after {
transform: translate(12px, 2px);
}

And Voila! The final result can be checked below. Note that additional styles were added to adjust the alignment between the switch and the label.

To improve even more the accessibility, images can be added to help users identify the actual state of the switch without relying only on the colors, here I have a study using SVG images and animations to accomplish that.

The code can be checked here, the final solution presented uses CSS variables to control the sizes and positions of the elements inside the component, making it easy to customize.

Based on the --height variable size all the following are automatically calculated:

  • Switch body: width and border-radius
  • Handler (circle): radius and positions

So, to create larger or smaller switches, just change the --height size.

--

--