Replacing Radio Buttons Without Replacing Radio Buttons

Share this article

Forms elements! They’re a pain to style, aren’t they? It’s tempting to replace them altogether, with some custom markup and CSS of our own design. The trouble is, the resultant rat’s nest of divs and spans will lack the semantic and behavioral qualities that made the standard type="radio" input accessible.

<div class="radio-label">
  <div class="radio-input" data-checked="false" data-value="accessible"></div>
  accessibility?
</div>

This is just a lonely piece of text that says “accessibility?” Tragic, really. To make this even begin to work correctly again, we need to add all sorts of remedial WAI-ARIA semantics. In the immortal words of Iron Maiden, “can I play with madness?

<div class="radio-label" id="accessible-radio">
  <div class="radio-input" data-checked="false" data-value="accessible" 
       aria-labelledby="accessible-radio" role="checkbox" aria-checked="false">
  </div>
  accessibility?
</div>

Our example is still one hundred percent inaccessible because we have yet to cludge all of the conventional behaviors and key bindings established by the standard type="radio". This will require the tabindex attribute and JavaScript galore — and do you know what? I’m not even going to begin down that road.

What I have done is available as a CodePen demo, and to follow is an explanation of the technique.

See the Pen Replacing Radio Buttons by SitePoint (@SitePoint) on CodePen.

Note: If you’ve not used radio buttons with a keyboard before, know that you are able to focus the active button using the TAB key and change the active button using the UP and DOWN arrow keys. This is standard UA behavior, not a JavaScript emulation.

Use what’s already there

To think accessibly, you need to consider the HTML the interface and the CSS merely the appearance of that interface; the branding. Accordingly, we need to look for ways to seize control of UI aesthetics without relying on the recreation of the underlying markup that marks a departure from standards.

What do we know about radio buttons?

One thing we know about radio buttons is that they can be in either a checked or unchecked state. Never mind ARIA, this is just HTML’s checked attribute.

<label for="accessible">
  <input type="radio" value="accessible" name="quality" id="accessible"> accessible
</label>

<label for="pretty">
  <input type="radio" value="pretty" name="quality" id="pretty"> pretty
</label>

<label for="accessible-and-pretty">
  <input type="radio" value="pretty" 
         name="quality" id="accessible-and-pretty" checked> accessible and pretty
</label>

Fortuitously, we can express the checked state via the :checked pseudo-class in CSS:

[type="radio"]:checked {
   /* styles here */
}

Less fortuitously, there aren’t many properties we can place in this block that will actually be honored — especially not consistently across browsers. Radio buttons obstinately refuse to be bent to our will.

The adjacent sibling combinator

I love the adjacent sibling combinator with a passion that a man perhaps should not reserve for CSS selector expressions. It allows me to style elements according to the nature of the elements that precede them.

This is a powerful notion in regard to our radio buttons because it allows us to defer the appearance of state changes onto elements that can actually be styled easily.

[type="radio"]:checked + span {
   /* styles for a span proceeded by a checked radio button */
}

We will, of course, have to add span elements to the markup, but worse fates could befall the HTML.

<fieldset>
  <legend>Radio Control Quality</legend>
  <label for="accessible">
    <input type="radio" value="accessible" name="quality" id="accessible"> 
    <span>accessible</span>
  </label>

  <label for="pretty">
    <input type="radio" value="pretty" name="quality" id="pretty"> 
    <span>pretty</span>
  </label>

  <label for="accessible-and-pretty">
    <input type="radio" value="pretty" name="quality" id="accessible-and-pretty" checked> 
    <span>accessible and pretty</span>
  </label>
</fieldset>

We don’t want to actually style the label text, but we have created the necessary relationship to move visual feedback away from the <input>. The radio button styling will, in fact, be deferred to the <span> element’s ::before pseudo-content.

Hiding the radio button is just a case of employing an accessible hiding technique like that found in HTML5 Boilerplate’s CSS:

[type="radio"] {
  border: 0; 
  clip: rect(0 0 0 0); 
  height: 1px; margin: -1px; 
  overflow: hidden; 
  padding: 0; 
  position: absolute; 
  width: 1px;
}

But if it’s hidden, how can anyone click it? By nesting the radio button in a <label>, user agents make the <label> itself a handler for toggling the radio. This is a good technique in any case because it increases the “hit area” of an otherwise diminutive control.

The styling

As previously mentioned, we will be using pseudo content to forge our “radio button”. This way, we can treat the styling of the label text separately.

[type="radio"] + span::before {
  content: '';
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.25em;
  border-radius: 1em;
  border: 0.125em solid #fff;
  box-shadow: 0 0 0 0.15em #000;
  margin-right: 0.75em;
  transition: 0.5s ease all;
}

Note the use of border and box-shadow to create the concentric rings. The checked style subsequently transitions the box shadow’s radius spread and incorporates a green on/correct/selected/positive color; the kind that’s usually defined somewhere in your Sass variables.

[type="radio"]:checked + span::before {
  background: green;
  box-shadow: 0 0 0 0.25em #000;
}

Never forget

All that remains is to incorporate a focus style so that keyboard users can see which element is in their control. An outline on thin dotted on the <span> would suffice, but I have opted for a unicode arrow, pointing to the control via ::after. This visual feedback is more emphatic than browser vendors provide by default, helping to increase the accessibility of the focus state.

[type="radio"]:focus + span::after {
  content: '\0020\2190';
  font-size: 1.5em;
  line-height: 1;
  vertical-align: -0.125em;
}

IEH8

IE8 poses a problem because it neither supports the checked pseudo-class nor the box-shadow and border-radius that helped form our radios. The selector support can be polyfilled with a library like Selectivizr and the styles can be handled differently (perhaps a background image?) but my preferred strategy would probably be to harness graceful degradation. Sass or LESS can tersely isolate the problematic declaration blocks.

Note that enhancements to label such as cursor: pointer are applied to all browsers.

/* One radio button per line */
label {
  display: block;
  cursor: pointer;
  line-height: 2.5;
  font-size: 1.5em;
}

:not(.lt-ie9) {

    /* HTML5 Boilerplate accessible hidden styles */
    [type="radio"] {
      border: 0; 
      clip: rect(0 0 0 0); 
      height: 1px; margin: -1px; 
      overflow: hidden; 
      padding: 0; 
      position: absolute; 
      width: 1px;
    }

    [type="radio"] + span {
      display: block;
    }

    /* the basic, unchecked style */
    [type="radio"] + span::before {
      content: '';
      display: inline-block;
      width: 1em;
      height: 1em;
      vertical-align: -0.25em;
      border-radius: 1em;
      border: 0.125em solid #fff;
      box-shadow: 0 0 0 0.15em #000;

      margin-right: 0.75em;
      transition: 0.5s ease all;
    }

    /* the checked style using the :checked pseudo class */
    [type="radio"]:checked + span::before {
      background: green;
      box-shadow: 0 0 0 0.25em #000;
    }

    /* never forget focus styling */
    [type="radio"]:focus + span::after {
      content: '\0020\2190';
      font-size: 1.5em;
      line-height: 1;
      vertical-align: -0.125em;
    }
}

Conclusion

There you have it, a solution to themeable radio controls that uses a whole lot of this…

  • HTML
  • CSS

… and none of this:

  • JavaScript
  • WAI-ARIA
  • Wheel reinventing
  • Voodoo

See the Pen Replacing Radio Buttons by SitePoint (@SitePoint) on CodePen.

Naturally, the basic technique could be applied to checkbox controls as well, but you’d have to be mindful of the check (tick) design. So, what do you think? Plain unicode? Icon font? Background image? Or maybe a shape created entirely in CSS?

Frequently Asked Questions (FAQs) about Replacing Radio Buttons

What are the alternatives to traditional radio buttons in HTML?

There are several alternatives to traditional radio buttons in HTML. One of the most popular alternatives is the use of checkboxes. Checkboxes, like radio buttons, allow users to select one or more options from a list. However, unlike radio buttons, checkboxes do not require a default option to be selected. Another alternative is the use of dropdown menus. Dropdown menus are particularly useful when there are many options to choose from, as they can save space on the page.

How can I style radio buttons using CSS?

Styling radio buttons using CSS can be achieved in several ways. One common method is by using the :checked pseudo-class, which allows you to apply styles to a radio button when it is selected. Another method is by using the ::before and ::after pseudo-elements to create custom designs for your radio buttons.

Can I use JavaScript to enhance the functionality of radio buttons?

Yes, JavaScript can be used to enhance the functionality of radio buttons. For example, you can use JavaScript to dynamically change the options available in a set of radio buttons based on the user’s previous selections. You can also use JavaScript to validate the user’s selections before they submit a form.

How can I make radio buttons more accessible?

Making radio buttons more accessible can be achieved by using the

What are the best practices for using radio buttons in forms?

Some best practices for using radio buttons in forms include: always use a

element; provide a default selection if possible; and ensure that the radio buttons are large enough to be easily clicked or tapped on touch devices.

Can I use radio buttons in responsive design?

Yes, radio buttons can be used in responsive design. You can use media queries in your CSS to adjust the size and layout of the radio buttons based on the screen size.

How can I create a custom radio button design?

Creating a custom radio button design can be achieved by using CSS and JavaScript. You can use CSS to style the appearance of the radio button, and JavaScript to enhance its functionality.

What are the limitations of using radio buttons?

Some limitations of using radio buttons include: they can only be used for mutually exclusive choices; they can be difficult to use on touch devices if they are too small; and they can take up a lot of space on the page if there are many options.

How can I test the usability of my radio buttons?

Testing the usability of your radio buttons can be done through user testing. This involves observing users as they interact with your radio buttons and asking them for feedback. You can also use online tools to conduct usability testing.

Can I use radio buttons for multi-select options?

No, radio buttons are designed for single select options. If you need to allow users to select multiple options, you should use checkboxes instead.

Heydon PickeringHeydon Pickering
View Author

Heydon is a UX designer who prototypes and programs accessible interfaces. He has a love/hate relationship with CSS and a lust/indifference relationship with JavaScript. He's the author of Apps For All: Coding Accessible Web Applications and you can find him as @heydonworks on Twitter.

accessible formsLouisLradio buttons
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week