This solution is outdated. You can find the newest one on this page or download the example straight away. Find more experiments here.
Here’s an unobtrusive crossbrowser solution for checkbox and radio button styling that does not use any vendor-specific properties such as (-webkit-appearance), but relies entirely on official CSS properties and no JavaScript at all.
Older browsers that do not support the adjacent elements selector (E + E), :checked and :disabled pseudo classes or generated CSS content (via ::before, ::after and content:) will degrade gracefully to the default checkboxes and radio buttons. As the approach for checkboxes and radiobuttons is essentially the same, for the explanations we will use radio buttons only. On the provided demo page examples with both checkboxes and radio buttons are shown, though.
In order to capture the change event of the checkbox or radio button without JavaScript, we will wrap the control in a <label>...</label> tag instead of placing it adjacent to the label. This approach is valid and is actually recommended as more semantic and accessible than the classic approach for creating label-input box pairs and does not require the setting of a for attribute to the label:
<label> <input type="radio" name="radio-group-1" /> </label>
For the second step we need to create an element which we will manipulate with CSS depending of the checked state of the form control. This element will also be used as a text container for the label text associated with the radio button. So, we create a <span>...</span> next to the radio button:
<label> <input type="radio" name="radio-group-1" /><span>my custom radio button 1</span></label>
In order to query the <span>...</span> element depending on the checked state of the radio button we will use the adjacent element selector. In order to prevent the image sprite that contains the normal and checked states for our custom checkboxes and radio buttons from showing entirely (for example on multiline label texts) we will render a pseudo element in the beginning of our span, which dimensions will be equal to the height and width of the default browsers’ radio buttons and checkboxes and thus we will clip the sprite:
input[type="radio"] + span::before { content: ""; display: inline-block; width: 20px; height: 20px; background: url("sprite.png") no-repeat -20px 0; vertical-align: middle; }
For the checked state we will use the :checked pseudo class of the checkbox or radio button and will set new coordinates for the sprite where the “checked” image is:
input[type="radio"]:checked + span::before { background-position: -20px -20px; }
Finally, we need to hide browser’s default radiobuttons. Using display: none or visibility: hidden will not work in this case, because when hidden in this manner, form controls cannot get focus and thus – will not get checked, so we set position: absolute and opacity: 0:
input[type="radio"] { position: absolute; -moz-opacity: 0; -webkit-opacity: 0; opacity: 0; }
Styling The Disabled, Hover, Active and Focus States
By using the :disabled pseudo class we can style the disabled state of our skinned form elements. And why not add some mouseover effect by using the :hover pseudo class? Or apply focus dots for usability purposes via :focus and :active?
/* disabled form elements */ input[type="radio"]:disabled + span, input[type="checkbox"]:disabled + span, input[type="radio"]:disabled + span::before, input[type="checkbox"]:disabled + span::before { -moz-opacity: .4; -webkit-opacity: .4; opacity: .4; } /* focused and active form elements */ input[type="checkbox"]:focus + span::before, input[type="radio"]:focus + span::before, input[type="checkbox"]:active + span::before, input[type="radio"]:active + span::before { outline: dotted 1px #ccc; }
Wrapping Things Up
And finally, to wrap things up and to be able to have different checkbox and radio button “skins” on a single page we can apply a compound common class (.skinned-form-controls.skinned-form-controls-[skin-name]) name to the controls’ parent element – one that contains the layout settings (.skinned-form-controls) and the other – the actual skin settings (.skinned-form-controls-mac):
<form method="post" action="./" id="form-1" class="skinned-form-controlsskinned-form-controls-mac"> <h2>Checkboxes</h2> <ul> <li><label><input type="checkbox" /><span>unchecked checkbox</span></label></li> <li><label><input type="checkbox" checked="checked" /><span>checked checkbox</span></label></li> <li><label><input type="checkbox" disabled="disabled" /><span>unchecked and disabled checkbox</span></label></li> <li><label><input type="checkbox" checked="checked" disabled="disabled" /><span>checked and disabled checkbox</span></label></li> </ul> <h2>Radio Buttons</h2> <ul> <li><label><input type="radio" name="radio-button-group-1" /><span>radio button 1</span></label></li> <li><label><input type="radio" name="radio-button-group-1" checked="checked" /><span>radio button 2</span></label></li> <li><label><input type="radio" name="radio-button-group-1" checked="checked" /><span>radio button 4</span></label></li> <li><label><input type="radio" name="radio-button-group-1" disabled="disabled" /><span>radio button 4 (disabled)</span></label></li> </ul> </form>
Taking Care of the pre-Internet Explorer 9 Versions
To make sure that older versions of Internet Explorer degrade gracefully, we will apply the infamous \9 hack to set position: static real checkboxes and radio buttons so the layout does not break. No version prior to IE9 supports opacity, so we do not have override it in our special rule.
.skinned-form-controls input[type="checkbox"], .skinned-form-controls input[type="radio"] { position: static\9; }
Supported Browsers
- Internet Explorer 9
- FireFox
- Opera
- Google Chrome
- Apple Safari
Demo and Download
- View demo of the solution on this page
- Download the examples page
- Find more experiments here.
More Cool Stuff
- Ribbonbar Web UI Component
- Acid.JS Web.UI Library
- CSS3 Treeview. No JavaScript.
- Selecting only the first element occurence out of siblings with the same class name with CSS3
- How to Style Select Boxes with CSS3
- CSS3 Element Reflections
- CSS3 iPhone Toggle Buttons
- CSS3 Gaussian Blur Effect
- Fancy CSS3 Tooltips Without JavaScript
- CSS3 Background Image Cropping

[...] newer and better crossbrowser solution for custom checkbox and radio button styling is available on this page, or you can go [...]
[...] This post was mentioned on Twitter by Jeff, PRO and John Richardson, junichi_y. junichi_y said: Custom Crossbrowser Styling for Checkboxes and Radio Buttons: Here’s a unobtrusive crossbrowser solution for che… http://bit.ly/ihzVE6 [...]
[...] Similar by Martin Ivanov [...]
Great hack, just what I needed. well, I did tweak it a bit for my needs, Thanks for the help. I assume this is free to use, nevertheless,I’ll ask, Can I use this in my website? also if you have any guidelines in using your hack? Very much appreciated. Thanks, Ira.
I’m glad you find the solution useful. Certainly you can use it on your website.
Great! I spent 5 days looking for cross browser custom radios. But I am having a problem with it though. I want to indent the label text 20px from the image.
.skinned-form-controls label span{
margin-left:20px;
}
The result is the whole thing is indented including the icons. However, the indention works on the fallback for old browser only. As many of us know, the fallback will not load the sprite so it is useless. How can I make the indention work for the new browsers?
Thanks
Matt