main content, site navigation, search

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:

26 shouts to “Fancy checkboxes and radio buttons”

  1. Fredrik Wärnsberg
    001—2006.06.12.00:13

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

  2. Denver
    002—2006.06.12.00:15

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

  3. marko
    003—2006.06.12.00:36

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

  4. Dennis Bunskoek
    004—2006.06.12.18:20

    Dear Marko,

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

  5. Yannick
    005—2006.06.12.18:51

    Dennis,

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

  6. Stephanie
    006—2006.06.12.19:50

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

  7. Sonja
    007—2006.06.12.20:25

    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. trovster
    008—2006.06.12.21:07

    ‘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. marko
    009—2006.06.12.21:20

    @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. Mauricio Samy Silva
    010—2006.06.12.22:13

    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. Mark
    011—2006.06.12.22:16

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

  12. Johan
    012—2006.06.12.23:24
  13. marko
    013—2006.06.13.00:09

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

  14. Yannick
    014—2006.06.13.05:20

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

  15. Johan
    015—2006.06.13.07:40

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

    I tebi hvala!

  16. trovster
    016—2006.06.13.10:12

    @ marko, what ‘fanaticism’?

  17. Michael Thompson
    017—2006.06.13.14:04

    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. marko
    018—2006.06.13.15:00

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

  19. Koen
    019—2006.06.13.20:04

    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. Koen
    020—2006.06.13.20:06

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

  21. marko
    021—2006.06.13.20:26

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

  22. brandon
    022—2006.06.14.22:32

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

  23. Nick Fitzsimons
    023—2006.06.19.16:47

    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. Jens Meiert
    024—2006.06.21.14:56

    Delayed due to comment post problems:

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

  25. Jery
    025—2006.06.21.21:25

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

  26. marko
    026—2006.06.21.22:08

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

Comments are closed.

main content, site navigation, search