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.

ABOUT ME
りん
On this blog, I mainly share information about web development and programming, along with my daily thoughts and what I’ve learned. I aim to create a blog that lets readers enjoy both technology and everyday life, so I also include topics about daily experiences, books, and gourmet. I’d be delighted if you could drop by casually and find something useful or enjoyable here.