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
</label>
Styles
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.
Setup
Here we're just setting some default margins, display type and setting the material font.
@import url(http://fonts.googleapis.com/css?family=Open+Sans);
.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;
}
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;
}
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.
Top comments (3)
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...
That will work now 😅
You're very welcome 😁