If you are reading this, please get modern browser.
skip to main content | skip to main navigation | skip to secondary content

Fancy checkboxes and radio buttons

~ 11th June 2006. · 22:37 CET · permanent link · printer friendly ~

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:

26 Comments

  1. It would be nice to see that little javascript in action.

  2. Yes, the example of the techniques mentioned above would be immensely appreciated.

  3. I’ve put some sample sites at the end of the article. Thanks.

  4. Dear Marko,

    I don’t think the label should actually surround the input. Check this page: http://htmldog.com/reference/htmltags/label/

  5. Dennis,

    It can surround the input. There is nothing wrong with doing so.

  6. Those Hellgate checkboxes aren’t staying checked for me in Camino – I see the check mark briefly, but then it disappears.

  7. 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 (;

  8. ‘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.

  9. @trovster: From the link you provided, I can only read that the implicit label is the one without the for attribute, not automatically each one that is wrapped around the input element. In the example at hand, all the labels have the for attribute. 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.

  10. W3C specs says:

    “To associate a label with another control implicitly, the control element must be within the contents of the LABEL element. In this case, the LABEL may only contain one control element. The label itself may be positioned before or after the associated control.”

    See: http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.9.1

  11. Also see the original post regarding this technique, http://flog.co.nz/2005/04/27/arc-adams-radiocheckbox-customisation/

  12. http://slayeroffice.com/code/custom_checkbox/

    Just look at the code

  13. @Mark & Johan: Thanks for the links! Now we have three different approaches here.

  14. trovster & Mauricio: Thanks for pointing that out. I wasn’t aware that it was changed in the WCAG 2.0 Spec.

  15. @Mark & Johan: Thanks for the links! Now we have three different approaches here.

    I tebi hvala!

  16. @ marko, what ‘fanaticism’?

  17. 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. :)

  18. @Michael Thompson: All the CSS reposition is made after JS changes class names on the labels. Thanks for the remark, though.

  19. 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';

  20. Oh, and is there a workaround for the behaviour that i’m experiencing; when you click, the onclick event-handler is called twice?

  21. @Koen: Thanks for the remark, it seems that I copied generated source. Anyway, it’s updated now.

  22. seems like a lot of code for very little output, how’s it do in a mobile device?

  23. 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.

  24. Delayed due to comment post problems:

    Listen to Sonja. Breaking interface conventions and standard input elements asks for caution.

  25. Nice trick. But I’m with sonja on this. I would also be worried about usability and accessibility.

  26. @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.

Sorry, the comment form is closed at this time, but if you have anything to say, please send me a message.

* Please keep in mind that this is a personal web site and it does not reflect the position or opinion of my respective employers, organizations or partners.

Typetester – compare screen type Supported by Veer.

What is this?

A web log of Marko Dugonjić, web professional from Croatia. Topics covered:

Translate this site

German, Spanish, Italian, French or Japanese (via).

See you there!

Feel like buying a book?

Try with maratz.com aStore

Worth visiting

top of the page | skip to main content | skip to main navigation | skip to secondary content