Accessible Styled Form Controls

Switch Component: Radio Buttons


Last updated:

Pattern to create a two option radio button group, visually styled to resemble a switch component.

Pattern Demo

Away message
Visual design note

The inclusion of two labels makes this switch pattern unique from other single label switch components. The design tends to fall apart a bit if it can't fully fit on a narrow screen, so that should be taken into consideration if implementing this in your own projects.

Ideally there would be a visible overarching label (a legend) to provide better context as to what the switch was for.

Use an appropriate heading element (h1 to h6) within a legend to surface this group as part of the document outline.

Pattern Details

Pattern Markup
<fieldset class="radio-switch">
  <div class="radio-switch__inner">
    <input type="radio" name="lol" id="public">
    <label for="public">

    <input type="radio" name="lol" id="private">
    <label for="private">

The markup for this component relies on a standard radio button group setup. A fieldset surrounds the radio buttons to provide context as to what these two options refer to. Each input is placed before its label so sibling selectors can be used to modify the state of the switch UI, based on whether the input is :checked or not.

Affects on Screen Reader Announcements?

Being a custom styled radio button group, there's not much for screen readers to get tripped up on here. When testing with standard keyboard tabbing, and screen reader virtual cursor, there were no abnormal announcements from screen readers.

It should be noted that using standard visually hidden CSS to hide the native radio buttons off screen, or as a single pixel in the document, will mean that certain screen reader users might be unable to locate the radio button. For instance, those searching by touch, or using a mouse while a screen reader announces what is being hovered. To mitigate this, the radio buttons have been positioned on top of their styled labels (in this case the on and off visual labels). The radio buttons are still styled to be visually hidden, but with a very low opacity level. Note that NVDA does not announce the radio button role when hovered by mouse, regardless of it being styled or not, unless default NVDA settings are changed.

One bit of concern for the visual switch treatment is that there is implied UX that accompanies a switch. For example, a visual switch could be an actual role="switch", a checkbox, or a toggle button, all depending on the context in which it is used. Each of these implementations would have a consistent single label, and the state of the component is what changes. This particular pattern doesn't adhere to those setups.

Usage note:

This type of a switch UI is heavily reliant on the idea that there are two visible labels that represent the opposing ends of the switch. Being that the switch has two labels, it would be expected that each label could receive mouse click or a tap to change the state of the switch.

Ideally the switch itself would also allow for mouse click or finger tap to freely toggle the state as well, as that's an expected interaction for a switch UI.

For single label switches, typically a user can focus the switch and toggle it with either Space or Enter keys. With a radio button switch, arrow keys would be used instead, and Space or Enter keys would be largely irrelevant, beyond a screen reader first focusing a radio button in this component, and using such keys to enter forms mode, if their screen reader hadn't entered forms mode already.

Note: due to :focus-within not having full support for older browsers, a polyfill has been added to this demo.

Continue reading

For additional information about radio button switch components, I suggest reviewing On Designing and Building Toggle Switches, by Sara Soueidan.