Creating Custom Form Checkboxes and Radio Buttons with Just CSS!

June 21, 2012 at 8:14 pm By
Download Demo

In CSS we have many ways to style things in any way we want. When it gets to forms though, things get a little complicated. Text inputs are easy, but checkboxes and radio buttons are very difficult to style with CSS. In this tutorial I’ll show you exactly how to style these inputs and make awesome forms with just CSS!

The Basics

Effectively, a really good idea for styling checkboxes the only way to style checkboxes, radio buttons and drop downs is with this little piece of CSS:

appearance: none;

This will take all browser styles off those inputs and we can start to alter them as regular elements. They’ll still have the same properties as before, just without the Mac or Windows layout. Unfortunately support is once again, quite low. Webkit is the only browsers supporting this (Opera sort of supports it), which gives you about 40% of the market at the moment (according to Statcounter).

It’s okay though! We can make this stuff degrade quite nicely, so for users of Webkit things will look great, but Opera, IE and Firefox users will just get regular old forms.

What we can use

So what weapons can we use to create these effects? Well to create extra elements we can use :after, :before (so we don’t have to change the HTML that much). Then for radio buttons and checkboxes we can use :checked. There are a few other pseudo elements we can use as well (such as :hover). Using a mixture of these we can create some pretty cool custom forms.

Lets Begin

Recently there has been a bit of discussion on the web about :before and :after when using forms. The general consensus is that the fact that psuedo content works on forms in webkit is a bug because forms cannot hold content, and pseudo content comes after the real content in an element. Unfortunately, I am going to be using these quite a lot to get the effects we want. The best thing about this, of course, is users using unsupported browsers will just see basic form elements.

To get started, lets make a few checkboxes and radio buttons. I’m going to make two types of each, one being small and the other being large. These are just a bunch of inputs with id’s, names and classes, so nothing too fancy, just regular forms.


	<div>
		<label>Checkbox Small</label>
		<input type="checkbox" id="checkbox-1-1" class="regular-checkbox" />
		<nput type="checkbox" id="checkbox-1-2" class="regular-checkbox" />
		<input type="checkbox" id="checkbox-1-3" class="regular-checkbox" />
		<input type="checkbox" id="checkbox-1-4" class="regular-checkbox" />
	</div>
	<div>
		<label>Checkbox Big</label>
		<input type="checkbox" id="checkbox-2-1" class="regular-checkbox big-checkbox" />
		<input type="checkbox" id="checkbox-2-2" class="regular-checkbox big-checkbox" />
		<input type="checkbox" id="checkbox-2-3" class="regular-checkbox big-checkbox" />
		<input type="checkbox" id="checkbox-2-4" class="regular-checkbox big-checkbox" />
	</div>
	<div>
		<label>Radio Small</label>
		<div class="button-holder">
			<input type="radio" id="radio-1-1" name="radio-1-set" class="regular-radio" checked />
			<input type="radio" id="radio-1-2" name="radio-1-set" class="regular-radio" />
			<input type="radio" id="radio-1-3" name="radio-1-set" class="regular-radio" />
			<input type="radio" id="radio-1-4" name="radio-1-set" class="regular-radio" />
		</div>
	</div>
	<div>
		<label class="radio-1">Radio Big</label>
		<div class="button-holder">
			<input type="radio" id="radio-2-1" name="radio-2-set" class="regular-radio big-radio" />
			<input type="radio" id="radio-2-2" name="radio-2-set" class="regular-radio big-radio" />
			<input type="radio" id="radio-2-3" name="radio-2-set" class="regular-radio big-radio" checked />
			<input type="radio" id="radio-2-4" name="radio-2-set" class="regular-radio big-radio" />
			<input type="radio" id="radio-2-5" name="radio-2-set" class="regular-radio big-radio" />
		</div>
	</div>

The next step is some CSS. To begin we style the normal checkbox. We remove any default styles with the appearance property, and then just style as we see fit. I’ve added a few box shadows and borders.


.regular-checkbox {
	-webkit-appearance: none;
	background-color: #fafafa;
	border: 1px solid #cacece;
	box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05);
	padding: 9px;
	border-radius: 3px;
	display: inline-block;
	position: relative;
}

After that we set the styles for when the user clicks on the checkbox (:active) and when the checkbox is checked.


.regular-checkbox:active, .regular-checkbox:checked:active {
	box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px 1px 3px rgba(0,0,0,0.1);
}

.regular-checkbox:checked {
	background-color: #e9ecee;
	border: 1px solid #adb8c0;
	box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05), inset 15px 10px -12px rgba(255,255,255,0.1);
	color: #99a1a7;
}

Then we just need to make an :after pseudo element. This will contain the check mark (for when the box is checked) by using content.


.regular-checkbox:checked:after {
	content: '\2714';
	font-size: 14px;
	position: absolute;
	top: 0px;
	left: 3px;
	color: #99a1a7;
}

And then it’s just a matter of some resizing for the big checkboxes


.big-checkbox {
	padding: 18px;
}

.big-checkbox:checked:after {
	font-size: 28px;
	left: 6px;
}

The radio buttons follow a similar pattern, only this time we use rounded corners and an actual element rather than text for the highlighted radio button. I’m sure you can figure it out.


.regular-radio {
	-webkit-appearance: none;
	background-color: #fafafa;
	border: 1px solid #cacece;
	box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05);
	padding: 9px;
	border-radius: 50px;
	display: inline-block;
	position: relative;
}

.regular-radio:checked:after {
	content: ' ';
	width: 12px;
	height: 12px;
	border-radius: 50px;
	position: absolute;
	top: 3px;
	background: #99a1a7;
	box-shadow: inset 0px 0px 10px rgba(0,0,0,0.3);
	text-shadow: 0px;
	left: 3px;
	font-size: 32px;
}

.regular-radio:checked {
	background-color: #e9ecee;
	color: #99a1a7;
	border: 1px solid #adb8c0;
	box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05), inset 15px 10px -12px rgba(255,255,255,0.1), inset 0px 0px 10px rgba(0,0,0,0.1);
}

.regular-radio:active, .regular-radio:checked:active {
	box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px 1px 3px rgba(0,0,0,0.1);
}

.big-radio {
	padding: 16px;
}

.big-radio:checked:after {
	width: 24px;
	height: 24px;
	left: 4px;
	top: 4px;
}

And we’re done! Not that difficult, eh? As I mentioned before, this will only work in webkit browsers but it gives those with the extra support some nice extra features. It will gracefully degrade in most browsers since we’re using padding, rather than defining the width and height which can lead to some odd styles in Firefox.

Internet Explorer assumes padding is around the input so in IE the buttons will have a border and padding surrounding the actual input, but it’s nothing life threatening. All things considered, the smaller buttons are perhaps more suitable if cross browser compatibility is the name of the game, merely because they are more aesthetically pleasing in Internet Explorer. Check out the demo in Chrome or Safari to see the final product.

Update! [Sept 11th 2012]

There has been a lot of talk about the cross browser compatibility of this page, and obviously there is very little to speak about. In the comments, Gunnar pointed out that using targeting a label or span after the form element would fix this problem, so I thought I’d show you guys how to do that. This requires a little more HTML.

We need to adjust the HTML so that there is a label after every input. Then we use the for tag to associate each label with their corresponding input. So our updated HTML looks like this:

	<div>
		<div class="tag">Checkbox Small</div>
		<input type="checkbox" id="checkbox-1-1" class="regular-checkbox" /><label for="checkbox-1-1"></label>
	</div>
	<div>
		<div class="tag">Checkbox Big</div>
		<input type="checkbox" id="checkbox-2-1" class="regular-checkbox big-checkbox" /><label for="checkbox-2-1"></label>
	</div>
	<div>
		<div class="tag">Radio Small</div>
		<div class="button-holder">
			<input type="radio" id="radio-1-1" name="radio-1-set" class="regular-radio" checked /><label for="radio-1-1"></label>
		</div>
	</div>
	<div>
		<div class="tag">Radio Big</div>
		<div class="button-holder">
			<input type="radio" id="radio-2-1" name="radio-2-set" class="regular-radio big-radio" /><label for="radio-2-1"></label>
		</div>
	</div>

Next we alter the CSS slightly so that the CSS targets the label, not the actual input. We don’t have to use the experimental appearance tag either! So instead of writing what we wrote before, .regular-checkbox, we’re going to write:

.regular-checkbox + label { }

We’re also going to set the checkboxes and radio buttons so they don’t display. The updated CSS looks like this:

label {
	display: inline;
}

.regular-checkbox {
	display: none;
}

.regular-checkbox + label {
	background-color: #fafafa;
	border: 1px solid #cacece;
	box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05);
	padding: 9px;
	border-radius: 3px;
	display: inline-block;
	position: relative;
}

.regular-checkbox + label:active, .regular-checkbox:checked + label:active {
	box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px 1px 3px rgba(0,0,0,0.1);
}

.regular-checkbox:checked + label {
	background-color: #e9ecee;
	border: 1px solid #adb8c0;
	box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05), inset 15px 10px -12px rgba(255,255,255,0.1);
	color: #99a1a7;
}

.regular-checkbox:checked + label:after {
	content: '\2714';
	font-size: 14px;
	position: absolute;
	top: 0px;
	left: 3px;
	color: #99a1a7;
}


.big-checkbox + label {
	padding: 18px;
}

.big-checkbox:checked + label:after {
	font-size: 28px;
	left: 6px;
}

.tag {
	font-family: Arial, sans-serif;
	width: 200px;
	position: relative;
	top: 5px;
	font-weight: bold;
	text-transform: uppercase;
	display: block;
	float: left;
}

.radio-1 {
	width: 193px;
}

.button-holder {
	float: left;
}

/* RADIO */

.regular-radio {
	display: none;
}

.regular-radio + label {
	-webkit-appearance: none;
	background-color: #fafafa;
	border: 1px solid #cacece;
	box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05);
	padding: 9px;
	border-radius: 50px;
	display: inline-block;
	position: relative;
}

.regular-radio:checked + label:after {
	content: ' ';
	width: 12px;
	height: 12px;
	border-radius: 50px;
	position: absolute;
	top: 3px;
	background: #99a1a7;
	box-shadow: inset 0px 0px 10px rgba(0,0,0,0.3);
	text-shadow: 0px;
	left: 3px;
	font-size: 32px;
}

.regular-radio:checked + label {
	background-color: #e9ecee;
	color: #99a1a7;
	border: 1px solid #adb8c0;
	box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05), inset 15px 10px -12px rgba(255,255,255,0.1), inset 0px 0px 10px rgba(0,0,0,0.1);
}

.regular-radio + label:active, .regular-radio:checked + label:active {
	box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px 1px 3px rgba(0,0,0,0.1);
}

.big-radio + label {
	padding: 16px;
}

.big-radio:checked + label:after {
	width: 24px;
	height: 24px;
	left: 4px;
	top: 4px;
}

This version of the code works in the latest version of all browsers (IE, Firefox, Opera and Chrome), so you don’t have to worry about whether or not it will work. I’ve updated the download and demo links so that you can see the updated version of the code. If you’ve enjoyed this, please follow us on twitter!

Hey! If you enjoyed this check out our post on creating more custom checkboxes, where we create a bunch of cool, custom checkboxes for use on your next project.