Styling checkboxes and radio buttons using CSS

The styling of checkboxes and radio buttons became possible with the introduction of the :checked pseudo-class in CSS3. This page describes two techniques: an image-based method, shown in the demonstration below, and a pure CSS method.

Download examples

The archives below contain two sets of example files demonstrating both the image-based and pure CSS styling methods described on this page.

File Size Description
examples.zip 2,812 bytes Zip archive
examples.7z 1,896 bytes 7-Zip archive

Image-based styling

Image-based styling allows the greatest flexibility in appearance. In the example above we combine the images for the three states (unchecked, selected checkbox, and selected radio button) into a single image for faster loading:

HTML of the following form is used for each checkbox or radio button:

1
2
3
4
<div>
  <input id="option" type="checkbox" name="field" value="option">
  <label for="option">Value</label>
</div>

The input and label elements are shown here on separate lines to increase readability; they should usually be put on the same line, with no intervening space, for more accurate styling.

We hide the checkboxes and radio buttons in the stylesheet:

1
2
3
4
5
6
7
input[type=checkbox]:not(old),
input[type=radio   ]:not(old){
  width   : 28px;
  margin  : 0;
  padding : 0;
  opacity : 0;
}

The selectors in lines 1 and 2 use the negation pseudo-class to hide the rule from older browsers. Lines 3 to 5 set the width, margin, and padding, in order to be able to position the alternative graphics accurately. Line 6 sets the opacity to render the standard user interface invisible.

We then position the label and display the unchecked image:

1
2
3
4
5
6
7
8
input[type=checkbox]:not(old) + label,
input[type=radio   ]:not(old) + label{
  display      : inline-block;
  margin-left  : -28px;
  padding-left : 28px;
  background   : url('checks.png') no-repeat 0 0;
  line-height  : 24px;
}

Line 3 sets the label to display as an inline block element, allowing line 7 to set its height to the height of the alternative graphics and centre the text vertically. Line 4 uses a negative margin to cover the space where the standard user interface would be displayed, and line 5 then uses padding to restore the label text to the correct position. The 28 pixel value used here and in the previous rule is equal to the width of the image plus some additional padding so that the label text is not too close to the image. Line 6 displays the unchecked image in the space before the label text.

Finally, we display the selected images when the checkboxes and radio buttons are selected:

1
2
3
4
5
6
7
input[type=checkbox]:not(old):checked + label{
  background-position : 0 -24px;
}

input[type=radio]:not(old):checked + label{
  background-position : 0 -48px;
}

Because we have combined the images for the various states into a single image, these rules modify the background position to show the appropriate image.

Pure CSS styling

The demonstration below is styled purely using CSS. Unlike the image-based method, the pure CSS method scales with the text size.

The HTML used for each checkbox or radio button is similar to that in the image-based method:

1
2
3
4
<div>
  <input id="option" type="checkbox" name="field" value="option">
  <label for="option"><span><span></span></span>Value</label>
</div>

The span elements inside the label are used to create the alternative graphics. While radio buttons require both spans, only one is needed for checkboxes.

We hide the checkboxes and radio buttons in the stylesheet:

1
2
3
4
5
6
7
8
input[type=checkbox]:not(old),
input[type=radio   ]:not(old){
  width     : 2em;
  margin    : 0;
  padding   : 0;
  font-size : 1em;
  opacity   : 0;
}

The technique is the same as in the image-based method, but because the width is set relative to the font size in line 3 we must restore the font size in line 6, as browsers use a smaller font size for checkboxes and radio buttons by default.

We then position the label:

1
2
3
4
5
6
input[type=checkbox]:not(old) + label,
input[type=radio   ]:not(old) + label{
  display      : inline-block;
  margin-left  : -2em;
  line-height  : 1.5em;
}

Again, the technique is the same as in the image-based method, but using relative units. The padding is not required as the scalable graphics, unlike a background image, will push the label text along.

We then style the first span to create the unchecked graphics:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
input[type=checkbox]:not(old) + label > span,
input[type=radio   ]:not(old) + label > span{
  display          : inline-block;
  width            : 0.875em;
  height           : 0.875em;
  margin           : 0.25em 0.5em 0.25em 0.25em;
  border           : 0.0625em solid rgb(192,192,192);
  border-radius    : 0.25em;
  background       : rgb(224,224,224);
  background-image :    -moz-linear-gradient(rgb(240,240,240),rgb(224,224,224));
  background-image :     -ms-linear-gradient(rgb(240,240,240),rgb(224,224,224));
  background-image :      -o-linear-gradient(rgb(240,240,240),rgb(224,224,224));
  background-image : -webkit-linear-gradient(rgb(240,240,240),rgb(224,224,224));
  background-image :         linear-gradient(rgb(240,240,240),rgb(224,224,224));
  vertical-align   : bottom;
}

The techniques used here are described in detail in the page on styling buttons with CSS3. Line 15 ensures the graphics are positioned at the bottom of the label rather than the baseline of the text.

In the example, the background gradient is reversed on selected checkboxes and radio buttons:

1
2
3
4
5
6
7
8
input[type=checkbox]:not(old):checked + label > span,
input[type=radio   ]:not(old):checked + label > span{
  background-image :    -moz-linear-gradient(rgb(224,224,224),rgb(240,240,240));
  background-image :     -ms-linear-gradient(rgb(224,224,224),rgb(240,240,240));
  background-image :      -o-linear-gradient(rgb(224,224,224),rgb(240,240,240));
  background-image : -webkit-linear-gradient(rgb(224,224,224),rgb(240,240,240));
  background-image :         linear-gradient(rgb(224,224,224),rgb(240,240,240));
}

We then display a tick inside selected checkboxes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
input[type=checkbox]:not(old):checked + label > span:before{
  content     : '✓';
  display     : block;
  width       : 1em;
  color       : rgb(153,204,102);
  font-size   : 0.875em;
  line-height : 1em;
  text-align  : center;
  text-shadow : 0 0 0.0714em rgb(115,153,77);
  font-weight : bold;
}

The selector in line 1 uses the :before pseudo-class so that line 2 can insert a tick symbol inside the span element. Lines 3, 4, 6, 7, and 8 display the tick centrally within the element, while lines 5, 9, and 10 style the text.

Finally, we display a ‘bullet’ inside selected radio buttons, applying the same techniques as for the unchecked graphics to the second span element:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
input[type=radio]:not(old):checked + label > span > span{
  display          : block;
  width            : 0.5em;
  height           : 0.5em;
  margin           : 0.125em;
  border           : 0.0625em solid rgb(115,153,77);
  border-radius    : 0.125em;
  background       : rgb(153,204,102);
  background-image :    -moz-linear-gradient(rgb(179,217,140),rgb(153,204,102));
  background-image :     -ms-linear-gradient(rgb(179,217,140),rgb(153,204,102));
  background-image :      -o-linear-gradient(rgb(179,217,140),rgb(153,204,102));
  background-image : -webkit-linear-gradient(rgb(179,217,140),rgb(153,204,102));
  background-image :         linear-gradient(rgb(179,217,140),rgb(153,204,102));
}

Where now?

Found this useful? Share it:

Also in HTML and CSS: