Building Accessible Toggle Buttons in React
Filter options, system preferences or a dark mode control – it's very likely that you've come across a toggle button before. At first sight, toggle buttons are simple: They allow the user to change a setting between two states, usually “on” and “off”.
Unfortunately, there's no native HTML element for toggle buttons. Which is why there's a lot of different custom implementations roaming the internet. Some of them, you guessed it, totally inaccessible nightmares.
Photo: © Gaby Tenda / pexels.com
I created a demo app in React with accessible toggle buttons, which I'd like to show you. I'm not sure that it's the perfect solution. So I'm eager for some feedback.
Demo: Beer List with Toggle Buttons
In my demo, I display a list of beers. Users can switch between weak and strong beers using toggle buttons. Give it a try:
If you inspect the code, you'll see that I used a group of buttons and applied
the switch
role to them. Before we take a closer look at the technical details,
I want to explain what I wanted to achieve with the control.
Goals and Requirements
- The control should clearly convey its functionality, both visually and to assistive technologies.
- It should indicate its current state, both visually and to assistive technologies.
- Users should be able to operate the control with the keyboard alone, duh!
- Only one option (“weak” or “strong”) can be active at one time. If one option is switched on, the other one is switched off.
- Switching between the two options should be a deliberate act by the user and not happen by accident.
Semantic Markup and Keyboard Operability
You could use radio buttons and change their styling with CSS. I decided not to use them because they're automatically selected on focus (not what I want!) and they're standard form elements. Web forms should not trigger a change of context until hitting the submit button.
So, I decided to use a button
element for each selectable option (“weak” and “strong”)
and apply the switch
role. The specification
for the role states:
A type of checkbox that represents on/off values, as opposed to checked/unchecked values.
My React component
contains a toggle button for each option. This way, I can label them individually and clearly convey their
state (on or off). Furthermore, the two buttons are grouped inside a section
element
with the group
role. Take a look at the JSX code:
<section className={styles.container} role="group" aria-label={switchLabel}>
<p>{switchLabel}:</p>
<div className={styles.switchWrapper}>
<button
type="button"
role="switch"
aria-checked={selectedState === stateValues[0]}
onClick={toggleSwitch}
>
{stateLabels[0]}
</button>
<button
type="button"
role="switch"
aria-checked={selectedState === stateValues[1]}
onClick={toggleSwitch}
>
{stateLabels[1]}
</button>
</div>
</section>
Users can focus the buttons with the tab key and toggle the state with the space or enter key.
Custom Styling with CSS
The toggle buttons' styling visually conveys their functionality and current state. When an option is
selected, I invert its colors and add a vertical line using the ::after
pseudo-element.
Here's an excerpt from my component's SCSS file:
/* Invert background and text colors */
button[aria-checked="true"] {
background: var(--light-black);
color: white;
}
/* Add vertical line to highlight selected option */
button[aria-checked="true"]:after {
content: "";
position: absolute;
bottom: 0.2em;
top: 0.2em;
}
button[aria-checked="true"]:first-of-type:after {
right: 0.2em;
border-right: 0.3em solid var(--primary-color);
}
button[aria-checked="true"]:last-of-type:after {
left: 0.2em;
border-left: 0.3em solid var(--primary-color);
}
Feedback
So, that's it. My proposed solution for accessible toggle buttons. Do you love it, do you hate it? Hit me up on Twitter, Mastodon or GitHub!
Useful Resources
- ARIA: switch role (MDN)
- WAI-ARIA 1.2 (specification)
- Building Accessible Toggle Buttons (Alternative Approach by Josh Collinsworth)
Posted on