Fancy checkboxes and radio buttons
Many young guns ask about how to style custom checkboxes and radio buttons in forms. I prepared a typical markup, a few lines of CSS and some JavaScript functions (Safari label behavior fix included).
The structure
Each radio button and/or checkbox input element should be surrounded with <label> tags. Here’s the example:
<label class="label_check" for="sample"><input name="sample" id="sample" value="1" type="checkbox" /> Sample Label</label> <label class="label_radio" for="sample"><input name="sample" id="sample" value="1" type="radio" /> Sample Label</label>
The presentation
We are going to remove inputs far away to the left and instead place a background image to each label. Radios and checkboxes will be toggled, because clicking/spacepressing the corresponding label toggles them on or off.
See the CSS sample:
label.c_off,
label.r_off,
label.c_on,
label.r_on { padding-left: 20px; }
label.c_off input,
label.r_off input,
label.c_on input,
label.r_on input { position: absolute; left: -9999px; }
label.r_off { background: url(radio_off.gif); }
label.c_off { background: url(check_off.gif); }
label.c_on { background: url(check_on.gif); }
label.r_on { background: url(radio_on.gif); }
The behavior
And finally, some JavaScript trickery to handle all the className switching. In Safari, labels are not clickable, hence a few extra Safari speciffic lines.
var d = document;
var safari = (navigator.userAgent.toLowerCase().indexOf('safari') != -1) ? true : false;
var gebtn = function(parEl,child) { return parEl.getElementsByTagName(child); };
onload = function() {
if(!d.getElementById || !d.createTextNode) return;
var ls = gebtn(d,'label');
for (var i = 0; i < ls.length; i++) {
var l = ls[i];
if (l.className.indexOf('label_') == -1) continue;
var inp = gebtn(l,'input')[0];
if (l.className == 'label_check') {
l.className = (safari && inp.checked == true || inp.checked) ? 'label_check c_on' : 'label_check c_off;
l.onclick = check_it;
};
if (l.className == 'label_radio') {
l.className = (safari && inp.checked == true || inp.checked) ? 'label_radio r_on' : 'label_radio r_off';
l.onclick = turn_radio;
};
};
};
var check_it = function() {
var inp = gebtn(this,'input')[0];
if (this.className == 'label_check c_off' || (!safari && inp.checked)) {
this.className = 'label_check c_on';
if (safari) inp.checked = true;
} else {
this.className = 'label_check c_off';
if (safari) inp.checked = false;
};
};
var turn_radio = function() {
var inp = gebtn(this,'input')[0];
if (this.className == 'label_radio r_off' || inp.checked) {
var ls = gebtn(this.parentNode,'label');
for (var i = 0; i < ls.length; i++) {
var l = ls[i];
if (l.className.indexOf('label_radio') == -1) continue;
l.className = 'label_radio r_off';
};
this.className = 'label_radio r_on';
if (safari) inp.checked = true;
} else {
this.className = 'label_radio r_off';
if (safari) inp.checked = false;
};
};
Also, be sure to check the previous post about how to preload all those interface graphics.
Update
Sample web sites:
- Nacional (‘remember me’ checkbox and search filter radio buttons)
- Hellgate: London (search filter checkboxes)

26 Comments
It would be nice to see that little javascript in action.
Comment (#) by Fredrik Wärnsberg — 12th June 2006.
Yes, the example of the techniques mentioned above would be immensely appreciated.
Comment (#) by Denver — 12th June 2006.
I’ve put some sample sites at the end of the article. Thanks.
Comment (#) by marko — 12th June 2006.
Dear Marko,
I don’t think the label should actually surround the input. Check this page: http://htmldog.com/reference/htmltags/label/
Comment (#) by Dennis Bunskoek — 12th June 2006.
Dennis,
It can surround the input. There is nothing wrong with doing so.
Comment (#) by Yannick — 12th June 2006.
Those Hellgate checkboxes aren’t staying checked for me in Camino – I see the check mark briefly, but then it disappears.
Comment (#) by Stephanie — 12th June 2006.
I would also be worried about usability with this. I’m just waiting for the first person to use an unhappy clown as an unchecked and a happy clown as a checked radio button. Some times the ‘nice’ submit button is in disguise already.
I’m not saying don’t use this. I’m saying think about your users for a bit (;
Comment (#) by Sonja — 12th June 2006.
‘nothing wrong’ is not entirely correct. The (now slated) second version Web Content Accessibility Guidelines states that http://www.w3.org/TR/2005/WD-WCAG20-HTML-TECHS-20050630/#label implicit form labels are deprecated in favour of explicit labels.
The example are pretty, but I’d like to see a pure test case showcasing this technique. A lot of people will be able to use this as trail and error example, instead of trawling through an entire sites CSS/HTML and JavaScript.
I would also like to see all the form-related images combined like in the http://wellstyled.com/css-nopreload-rollovers.html Pixy no preload image method.
Comment (#) by trovster — 12th June 2006.
@trovster: From the link you provided, I can only read that the implicit
labelis the one without theforattribute, not automatically each one that is wrapped around theinputelement. In the example at hand, all thelabels have theforattribute. Anybody else on that subject?Pixy’s technique is certainly very useful, but why the such fanaticism? : )
@Stephanie: Which version are you looking in? Everything seems to be alright in the latest stable Camino on my Mac.
@Sonja: You’re absolutely right, I’m not encouraging anybody here, just providing a solution.
Comment (#) by marko — 12th June 2006.
W3C specs says:
See: http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.9.1
Comment (#) by Mauricio Samy Silva — 12th June 2006.
Also see the original post regarding this technique, http://flog.co.nz/2005/04/27/arc-adams-radiocheckbox-customisation/
Comment (#) by Mark — 12th June 2006.
http://slayeroffice.com/code/custom_checkbox/
Just look at the code
Comment (#) by Johan — 12th June 2006.
@Mark & Johan: Thanks for the links! Now we have three different approaches here.
Comment (#) by marko — 13th June 2006.
trovster & Mauricio: Thanks for pointing that out. I wasn’t aware that it was changed in the WCAG 2.0 Spec.
Comment (#) by Yannick — 13th June 2006.
I tebi hvala!
Comment (#) by Johan — 13th June 2006.
@ marko, what ‘fanaticism’?
Comment (#) by trovster — 13th June 2006.
Wow, tens of lines of extraneous code to style a checkbox? And it’s still not guaranteed to work in all browsers (and I mean visually work, a checked box is a checked box)?
Where can I sign up? I can see where this is appropriate and fun for entertainment sites; but if I ever see this anywhere else I’m leaving the web.
Any mobile devices with half of a CSS brain will position the input appropriately (off-screen), style the label’s background (unchecked until the JS runs), and most likely never be able to touch and/or handle the JS effectively.
Creative, though. :)
Comment (#) by Michael Thompson — 13th June 2006.
@Michael Thompson: All the CSS reposition is made after JS changes class names on the
labels. Thanks for the remark, though.Comment (#) by marko — 13th June 2006.
Youre HTML example is not correct, you have already filled the class with c_off or c_on. But as you are mentioning, the JS adds this extra classname automatically. And because you’re checking for className’s that contain exaclty ‘label_check’ the script won’t work.
Btw, this line:
l.className = (safari) ? (inp.checked == true) ? 'label_check c_on' : 'label_check c_off' : (inp.checked) ? 'label_check c_on' : 'label_check c_off';is able to be shorter:
l.className = (safari && inp.checked == true || inp.checked) ? 'label_check c_off' : 'label_check c_on';Comment (#) by Koen — 13th June 2006.
Oh, and is there a workaround for the behaviour that i’m experiencing; when you click, the onclick event-handler is called twice?
Comment (#) by Koen — 13th June 2006.
@Koen: Thanks for the remark, it seems that I copied generated source. Anyway, it’s updated now.
Comment (#) by marko — 13th June 2006.
seems like a lot of code for very little output, how’s it do in a mobile device?
Comment (#) by brandon — 14th June 2006.
I haven’t checked this in depth, but I heard recently that a better check for Safari is to test the userAgent for “WebKit” (or “webkit” in your code, as you use “toLowerCase"), as the code will then work in other applications that use the Apple HTML rendering component - for example, NetNewsWire.
Comment (#) by Nick Fitzsimons — 19th June 2006.
Delayed due to comment post problems:
Listen to Sonja. Breaking interface conventions and standard input elements asks for caution.
Comment (#) by Jens Meiert — 21st June 2006.
Nice trick. But I’m with sonja on this. I would also be worried about usability and accessibility.
Comment (#) by Jery — 21st June 2006.
@Jens & Jery: See my previous comment. I’m not representing this technique in a way of the good or the bad practice, just providing you with a solution.
Comment (#) by marko — 21st June 2006.
Sorry, the comment form is closed at this time, but if you have anything to say, please send me a message.