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.

A guy standing behind a DJ desk with many buttons and knobs. 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

  1. The control should clearly convey its functionality, both visually and to assistive technologies.
  2. It should indicate its current state, both visually and to assistive technologies.
  3. Users should be able to operate the control with the keyboard alone, duh!
  4. Only one option (“weak” or “strong”) can be active at one time. If one option is switched on, the other one is switched off.
  5. 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

Posted on