Styling Checkboxes and Radio Buttons With CSS
This article covers how to style checkboxes and radio buttons with CSS.
The short answer: if you only want to change the colour, accent-color is a single line. Building the shape yourself starts with appearance: none.
▼How far do you need to go?
| Goal | Approach | Amount of CSS |
|---|---|---|
| Change the tick colour only | accent-color |
One line |
| Change the shape and size | appearance: none and build it |
About 20 lines |
| Add an icon | Font Awesome | A stylesheet plus a few lines |
Changing just the colour with accent-color
accent-color recolours the browser’s own checkbox and radio controls. No appearance: none required.
input[type="checkbox"],
input[type="radio"] {
accent-color: #66bb6a;
}
That is the whole change. The advantage is that you keep the operating system’s rendering and behaviour — the focus ring, the hit area, the platform conventions — none of which you have to rebuild.
It also applies to progress and range inputs. Support is Chrome 93, Safari 15.4 and Firefox 92 onwards.
If all you need is your brand colour on a form, check whether this is enough before going further. Everything below is for when the shape itself has to change.
Building custom checkboxes and radio buttons
The HTML
<input type="checkbox" id="customCheckbox">
<label for="customCheckbox">Custom checkbox</label>
<input type="radio" id="customRadio" name="radioGroup">
<label for="customRadio">Custom radio button</label>
The CSS
input[type="checkbox"] {
/* remove the browser's own rendering */
appearance: none;
-webkit-appearance: none;
width: 20px;
height: 20px;
border: 2px solid #999;
border-radius: 4px;
background-color: white;
display: inline-block;
vertical-align: middle;
position: relative;
}
input[type="checkbox"]:checked {
background-color: #66bb6a;
border-color: #66bb6a;
}
/* draw the tick yourself, because appearance: none removed it */
input[type="checkbox"]:checked::after {
content: "";
position: absolute;
top: 1px;
left: 5px;
width: 5px;
height: 10px;
border: solid #fff;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
input[type="radio"] {
appearance: none;
-webkit-appearance: none;
width: 20px;
height: 20px;
border: 2px solid #999;
border-radius: 50%;
background-color: white;
display: inline-block;
vertical-align: middle;
position: relative;
}
input[type="radio"]:checked {
background-color: white;
border-color: #42a5f5;
}
/* draw the inner dot */
input[type="radio"]:checked::after {
content: "";
position: absolute;
top: 4px;
left: 4px;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #42a5f5;
}
appearance: none removes the tick mark along with everything else. Changing only the background colour leaves a control where the colour changes but no tick appears, so the state is conveyed by colour alone. The ::after rules above draw the mark back in.
You also have to restore the focus indicator, which is removed at the same time. Without it, keyboard users lose track of where they are.
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
outline: 2px solid #1976d2;
outline-offset: 2px;
}
:focus-visible shows the ring for keyboard navigation but not for mouse clicks.
Adding icons with Font Awesome
Font Awesome icons pair well with labels when you want the control to read at a glance.
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.0/css/all.min.css">
<input type="checkbox" id="iconCheckbox">
<label for="iconCheckbox"><i class="fa-solid fa-square-check"></i> Checkbox with an icon</label>
<input type="radio" id="iconRadio" name="radioGroup">
<label for="iconRadio"><i class="fa-solid fa-circle-dot"></i> Radio button with an icon</label>
input[type="checkbox"] + label i {
margin-right: 5px;
}
input[type="radio"] + label i {
margin-right: 5px;
}
This article originally used Font Awesome 4.7.0. Version 7 is current, and the class naming changed in version 5, so copying markup from older tutorials produces blank icons.
▼Class naming across versions
| Version 4 | Version 5 onwards | |
|---|---|---|
| Style prefix | fa |
fa-solid / fa-regular |
| Checked square | fa-check-square |
fa-square-check |
| Selected circle | fa-dot-circle-o |
fa-circle-dot |
| Stylesheet | font-awesome.min.css |
all.min.css |
Icon names were reorganised in version 5 into a consistent noun-then-modifier order. Version 4 class names do not work in version 7.
Which approach to use
Reach for accent-color first. Only move to appearance: none when the shape or size genuinely has to change.
Rebuilding a control from scratch means rebuilding everything the browser was giving you: the tick, the focus ring, the hit area, and the platform’s own conventions for how the control behaves. Each is easy to forget, and each is only noticed by the people who depend on it.
The same appearance: none reasoning applies to select elements — see how to centre text in a select element, where the native rendering has to be switched off for the same reason.
For toggling visibility of the surrounding UI, building an accordion menu covers the show-and-hide patterns.