DEV Community

Cover image for Accessibility first: radio buttons
Andrew Bone
Andrew Bone

Posted on • Updated on

Accessibility first: radio buttons

This will be the last element I make, for now, in my accessibility first series. We've made material design switches, material design input boxes and now we're about to make material design radio buttons.

The aim of this short series was to show that you can put accessibility first and still use nice design. Most of these have been quite simple, stylised version of their base element.

What do we want to create?

We'll be making a material design inspired radio button today. As always I'll be avoiding JavaScript, not because it's intrinsically bad but rather I want to lean on the platform as much as possible, this makes a better user experience.

If you don't know radio buttons are grouped by name and you tab to each named group. Tabbing to the group will only select the checked button but you can use the arrow keys to move focus and, subsequently, check the new focused button.

The markup

I have no doubt if you've seen any of my stuff you're used to the style now, label wrapping an input, which I will hide later.

<label class="md_radio">
  <input name="{{group name}}" type="radio" />
  <span class="md_radio__button"></span>
  Radio Button


There are so many similarities between the radio button and the switch that I won't go into so much detail with this post but I'll still give you the general flavour.

The states we have to account for are

  • Not checked
  • Checked
  • Disabled
  • Focused

Not checked

We'll do all the setup here too as not checked is the usual default state.


Here we're just setting some default margins, display type and setting the material font.

@import url(;
.md_radio {
  display: inline-flex;
  font-family: "Open Sans";
  align-items: center;
  margin: 5px 0;

.md_radio .md_radio__button {
  position: relative;
  cursor: pointer;
  margin: 0 3px;

The button

I'm using the ::before and ::after pseudo-classes to make the button, you'll notice my ::after styles are set but are then hidden using scale this is preventing us from having to paint every time a button is checked.

.md_radio .md_radio__button::before,
.md_radio .md_radio__button::after {
  content: '';
  box-sizing: border-box;
  display: block;
  transition: all 100ms cubic-bezier(0.4, 0.0, 0.2, 1);

.md_radio .md_radio__button::before {
  height: 1.2em;
  width: 1.2em;
  border-radius: 50%;
  border: 0.15em solid #BDBDBD;

.md_radio .md_radio__button::after {
  position: absolute;
  top: 50%;
  left: 50%;
  height: 0.65em;
  width: 0.65em;
  border-radius: 50%;
  transform-origin: 50%, 50%;
  transform: scale(0, 0) translate(-50%, -50%);
  background: #00897B;

Material Design Radio Buttons Not checked


Checked is super simple for this example, we want to scale up ::after and change the border colour of ::before and that's it.

.md_radio [type=radio]:checked+.md_radio__button::after {
  transform: scale(1, 1) translate(-50%, -50%);

.md_radio [type=radio]:checked+.md_radio__button::before {
  border-color: #00897B;

Material Design Radio Buttons Checked

Disabled and focuses

These styles are exactly the same as my switch example so here's the code.

/* Disabled */
.md_radio [type=radio]:disabled+.md_radio__button {
  cursor: not-allowed;
  filter: grayscale(100%);
  opacity: 0.6;

/* Focused */
.md_radio [type=radio]:focus+.md_radio__button {
  outline: #5d9dd5 solid 1px;
  box-shadow: 0 0 8px #5e9ed6;

Finishing up

Finally, we need to hide our original input I do this by making the position absolute, to take up no space, setting the opacity to 0 and turning off pointer events.

.md_radio [type=radio] {
  position: absolute;
  opacity: 0;
  pointer-events: none;

Thank you for coming along on this journey with me. I hope you can take something away from this an apply it to your own projects.

Discussion (3)

4lch4 profile image
Devin W. Leaman

I absolutely love these Accessibility First articles, thank you!

A slightly funny side-note, each time you post a picture of the resulting radio button, or whichever element you're building, I inevitably try and click on it. Every. Time. 🤦‍♂️

You'd think by now I'd realize it's an image...

link2twenty profile image
Andrew Bone Author

each time you post a picture of the resulting radio button, or whichever element you're building, I inevitably try and click on it.

That will work now 😅

link2twenty profile image
Andrew Bone Author

You're very welcome 😁