Posted in:

If you’ve ever needed to allow a user to select one from a range of options on a webpage, you’ll know that the radio input type lets you do this.

image

But what if you want to customize the appearance and instead of showing regular radio buttons you want clickable buttons like this?

image

Well, thanks to a very helpful StackOverflow answer I was able to work out how to create this style of radio buttons.

First of all, you start off by hiding the actual circular radio buttons themselves – we’ll just be styling the labels. We can select them by using input[type="radio"]. Initially I was hiding this by setting display:none, but as Patryk Kiedrowski points out in the comments, that will mean they are unfocusable and unable to be navigated via the keyboard. So instead, we make it invisible with the following settings:

.radio-toolbar input[type="radio"] {
  opacity: 0;
  position: fixed;
  width: 0;
}

Next, we set the labels up to look how we want them by default when unselected:

.radio-toolbar label {
    display: inline-block;
    background-color: #ddd;
    padding: 10px 20px;
    font-family: sans-serif, Arial;
    font-size: 16px;
    border: 2px solid #444;
    border-radius: 4px;
}

Now we have to style the selected one differently. This is where the real CSS magic happens – we need to use the :checked selector and the “adjacent sibling” selector (+ sign). So this CSS rule applies to any label that immediately follows a checked radio button.

.radio-toolbar input[type="radio"]:checked + label {
    background-color:#bfb;
    border-color: #4c4;
}

For accessibility reasons, we'd also like to change the appearance when a button has focus. We can use the same selector technique. In this example I'm simply making the border dashed.

.radio-toolbar input[type="radio"]:focus + label {
    border: 2px dashed #444;
}

Here's what it looks like focused. This allows you to use left and right arrows to change selection:

image

Finally, I wanted a hover effect so that as you hovered the mouse over the other options they changed appearance. This can be achieved with the :hover selector.

.radio-toolbar label:hover {
  background-color: #dfd;
}

Here’s a complete example:

Comments

Comment by Guthrie

Hey - really appreciate this writeup, I was in a search-hole until I stumbled upon it, and it made my day.
Thanks!

Guthrie
Comment by Mickey Puri

I was about to make a custom control, then thought perhaps a radio button group would do this if can style it apprpriately, this is just the ticket!

Mickey Puri
Comment by Natsu Shiyo

Actually so fucking thank you, i was searching for someone that styled his radios like this and found you, bless you my friend.

Natsu Shiyo
Comment by Patryk Kiedrowski

Actually, you should not hide the inputs by using display: none nor visibility: hidden, as it makes them unfocusable and disables the ability to use them with keyboard. What you should do is hide them by setting width and height to 0px and setting opacity to 0.

Patryk Kiedrowski
Comment by Mark Heath

thanks for the tip - I've updated the post.

Mark Heath
Comment by Tj Chambers

Same with me! Exactly what I needed.

Tj Chambers
Comment by Robert Muzzy

Thank you so much for this. It has helped greatly. Any idea how to if say the two choices are Approve and Deny have them defaulted to gray when initially loaded or have no value but when selected either be green for Approve and Red for Deny?
Thanks again

Robert Muzzy
Comment by Mark Heath

yes, just give each choice its own id, and then have a different CSS rule for the checked color of each one

Mark Heath
Comment by Robert Muzzy

Thank you

Robert Muzzy
Comment by Janet Neckel

Exactly what I was looking for, thank you!

Janet Neckel
Comment by Dennis Nolan

Really liked this, but the input element isn't firing a change event.
I had to add a click event to the label.
Also the checked attribute on the input[type="radio"] is not being either set or reset.
Therefore add the following click event listener to the radio-toolbar elements.
function radio_toolbar_click (ev){
let checked = document.querySelector('input[name="radioFruit"]:checked');
if(checked) {
checked.checked = false;
}
ev.target.previousElementSibling.checked = true;
}

Dennis Nolan
Comment by HarrisG

Awesome. It helped me very much. Thank you

HarrisG
Comment by Richard Nash

hmmmm...this is nice, but how would I go about doing the same thing if the input was not a sibling but a child of the label...this is how react recommends creating the html for radio buttons, but i would like to style it the same way that you have...

Richard Nash
Comment by Agata

try using an 'input' event

Agata
Comment by Santiago Sanchez

this is awesome, many thanks

Santiago Sanchez
Comment by paisley

I'm quite late here, but since no one responded earlier...
CSS can't select an ancestor (the label) from a descendant (the input), but a JavaScript EventListener could. (In this case, I think you'd want the "change" event):
const toolbar = document.querySelector('.radio-toolbar');
toolbar.addEventListener('change', handleFruitChanges);
function handleFruitChanges(event){
const changedThing = event.target;
if(changedThing.name == 'radioFruit'){
changedThing.parentElement.classList.toggle('checked-style');
}
}
...This assumes your CSS does something special to elements that have the `checked-style` class.
That said, not everyone agrees that nesting inputs inside labels is a best practice, so if someone felt like going cowboy and ignoring the React style guide on this, I probably wouldn't tattle on them.

paisley
Comment by Dave Powers

Very helpful, thank you! I think this writeup is worthy of its own answer on the linked Stack Overflow question. Happy to vote up if you submit it.

Dave Powers
Comment by Samanyu Mehra

Hello, I am creating these radio buttons dynamically so I cant use the label. Is there any other way I can add the value inside the button? Thanks in Advance

Samanyu Mehra
Comment by Kelum

i tried many ways to get the value of checked one using jquery, none of them functional. please help me how to get the value of checked radio using jquery. thanks

Kelum
Comment by Marcus Bondezan

Thx! You've helped me a lot!

Marcus Bondezan
Comment by Stefan Coetzee

Great, thank you. Works like a charm.

Stefan Coetzee
Comment by Allison Barnett

Everything works until I get to the :checked part. The background doesn't change at all but the buttons are still being selected.

Allison Barnett
Comment by yerfackingmammy

nabbed.

yerfackingmammy
Comment by kydkit

make sure your input's "id" attribute matches your label's "for" attribute

kydkit
Comment by Artur Baczewski

Could you please tell me more? It doesn't work for me neither. I know that the button is checked, but can't see it.

Artur Baczewski