This content originally appeared on DEV Community and was authored by Nic
I recently got given a design that included a toggle. But I know they are not the most accessible of components, so I had to do some research on the best ways to create them.
I started at https://www.w3.org/WAI/ARIA/apg/patterns/switch/. This site is really useful for telling you how custom components should behave and what things you need to make sure to include. And it has examples.
I went with the checkbox example, since the one I had to create had an on and an off state. In my case, the design had labels on either side of the toggle and the on one would show when the toggle was on and the off one when the toggle was off.
HTML
My HTML ended up like this:
<label>
<span class="sr-only">What this toggle is for</span>
<span aria-hidden="true" class="off">Off</span>
<input type="checkbox" role="switch" checked class="checkbox">
<span class="toggle">
<span class="toggle-button"></span>
</span>
<span aria-hidden="true" class="on">On</span>
</label>
All of the content is inside the label, so the whole thing is clickable. As with any input we need a label. The design doesn’t include one, so we need to include a span with the label and hide it from sighted users.
The checkbox is doing the actual work and the .toggle
and .toggle-button
make up the actual toggle. In the design I had the checkbox should be on by default, hence why the HTML includes the checked
attribute.
Then there’s the on/off texts, which are hidden from screen readers. This is because the screen reader will announce whether the checkbox is on or off. The toggle is really only an interesting way to show a checkbox to sighted users.
CSS
For the layout, I used a grid to put the elements all next to each other and add some code to change the opacity of the on/off texts based on the checkbox’s state.
label {
position: relative;
display: inline-grid;
grid-template-columns: repeat(3, auto);
align-items: center;
gap: .5rem;
}
.off {
opacity: 1;
transition: opacity 250ms ease;
}
.on {
opacity: 0;
transition: opacity 250ms ease;
}
.checkbox:checked ~ .on {
opacity: 1;
}
.off:has( ~ .checkbox:checked) {
opacity: 0;
}
:has
is useful here as a previous sibling selector. Although if it didn’t exist, we could just put the off text after the checkbox in the HTML and re-order the elements. Usually that would be a terrible idea, but in this case, they’re only there for sighted users and aren’t selectable, so it’s not going to make much difference.
And then there’s the toggle itself. I remembered last year hearing a talk from Sara Soueidan about hiding the default checkbox when you’re styling your own. Handily she also has a blog post about it at https://www.sarasoueidan.com/blog/inclusively-hiding-and-styling-checkboxes-and-radio-buttons/.
The important part of this post is that on Android you can explore a page by touch and feel interactive elements. Which means we can’t hide the checkbox by making it tiny or moving it off the page.
What we are doing instead is making the checkbox and toggle the same size and putting the toggle on top of the checkbox (and also adding some styling to make it look pretty).
:root {
--toggle-width: 52px;
--toggle-height: 30px;
--toggle-button-width: 26px;
}
.checkbox {
position: absolute;
width: var(--toggle-width);
height: var(--toggle-height);
opacity: 0;
grid-column-start: 2;
}
.toggle {
display: block;
position: relative;
width: var(--toggle-width);
height: var(--toggle-height);
border-radius: 30px;
padding: 0.125rem;
background-color: lightgrey;
cursor: pointer;
}
.toggle-button {
display: block;
position: relative;
top: 2px;
left: 2px;
width: var(--toggle-button-width);
aspect-ratio: 1;
border-radius: 50%;
background: white;
transition: left 250ms ease;
}
.checkbox:focus-visible + .toggle {
outline: 2px solid black;
outline-offset: 2px;
}
Using custom properties makes sure that the checkbox and toggle are the same size. And because this is in a grid we can make sure that the absolutely positioned checkbox is in the same grid column as the checkbox.
Although the checkbox is turned on by default, the CSS defaults to the checkbox being turned off. All we have left is to move the toggle button over the right and change the toggle’s colour when the checkbox is checked:
.checkbox:checked + .toggle {
background-color: red;
}
.checkbox:checked + .toggle > .toggle-button {
left: calc(var(--toggle-button-width) - 2px);
}
Final result
And it ends up looking like a standard toggle:
This content originally appeared on DEV Community and was authored by Nic

Nic | Sciencx (2025-02-12T19:33:23+00:00) Accessible toggle. Retrieved from https://www.scien.cx/2025/02/12/accessible-toggle/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.