Exploring an accessible switch component
Create an accessible and progressively enhanced switch component using HTML and CSS.
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 Safariborder: 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.