Building CampaignHawk: Radio Button Styling (Part 14)

Styling radio buttons is more involved than you might expect. This is because they have multiple states and, by necessity, must have a label that is associated with the button. These can all be handled with the CSS pseudo-classes :before:after:checked, and :not. We want buttons that look and function like the example below.

Another complicating factor is that it is not practical (as far as I know) to change the html default radio button into something more stylish. The only way to do this is to disable the default radio buttons and start from scratch. So the first step is to disable the default buttons.

In the code below, we’re finding all elements with the type “radio” that are either checked or not checked and telling them not to display.

[type="radio"]:checked {
display: none;

Now we need to style our radio buttons. Recall that by necessity these radio buttons will have a label associated with them. The “+” sign is a CSS selector that only applies the styling to an element (in this case, our radio button) that has the latter element (the label) immediately following.

None of the styling in here should be particularly new, but they are being used in interesting ways (which you will see later).

[type="radio"] + label {
position: relative;
cursor: pointer;
transition: all 0.3s ease;
padding: 3px 0 0 35px;
line-height: 25px;

Now we’re going to do a little bit of trickery. We want to allow the user to be able to click on either the button or the label to trigger a change. We can do this by wrapping the entire thing in a container element and triggering a change on click of the container element, or we can link them with the :before and :after selector, which I happen to think is a more elegant solution.

You’ll notice above that we gave the labels a position of relative with a padding of 35 pixels to the left so we can later position our radio buttons absolutely within that padding with our :before pseudo class. As you may recall from an earlier article, when you position something absolutely, it looks for the closest positioned element and positions itself absolutely relative to that element. You’ll see how this works below.

When using a :before or :after pseudo class, you can add a string as the content. For example, if you put “Reminder: ” as the content, then “Reminder: ” would appear before every element with the :before pseudo class. What we’re going to do is set the position of the :before element to be absolutely positioned to the top left of our label (within the 35px padding):

[type="radio"] + label:before {
content: '';
position: absolute;
left: 0;
top: 0;
margin: 4px;
width: 16px;
height: 16px;
transition: all 0.3s ease;

So now we have something that, upon inspection, looks like the image below, with an empty, absolutely-positioned element to the left of our label.

Now it’s time to style that empty element. Since these are radio buttons, we actually need to style them twice: once for the :checked status and once for the :not(:checked) status.

First let’s style the unchecked radio button. We’re going to make this circular by settings the border-radius to 50% and setting a width of 2px.

[type="radio"]:not(:checked) + label:before {
border-radius: 50%;
border: 2px solid #888;

Then we need to style the checked radio button. This is basically the same as the unchecked, except it has a background color that fills it.

[type="radio"]:checked + label:before {
border-radius: 50%;
border: 2px solid $default-text-color;
background-color: $default-text-color;

There are only a couple things we need to do now to finish our styling. We need to make sure our label text doesn’t wrap and that the content in our popup-container is properly padded and doesn’t have the default bullets for the list.

.popout-content {
padding: 10px 15px;
ul {
list-style-type: none;

label {
white-space: nowrap;

And that should do it for our radio buttons!

Next Steps

Now that we have fancy radio buttons, we need to connect them to some actual behavior. To do that, we need make some data layers that we can functionally call by clicking on a radio button.