Grow your CSS skills. Land your dream job.

Stuff you can do with the “Checkbox Hack”

Published by Chris Coyier

The "Checkbox Hack" is where you use a connected label and checkbox input and usually some other element you are trying to control, like this:

<label for="toggle-1">Do Something</label>
<input type="checkbox" id="toggle-1">
<div>Control me</div>

Then with CSS, you hide the checkbox entirely. Probably by kicking it off the page with absolute positioning or setting its opacity to zero. But just because the checkbox is hidden, clicking the <label> still toggles its value on and off. Then you can use the adjacent sibling combinator to style the <div> differently based on the :checked state of the input.

input[type=checkbox] {
   position: absolute;
   top: -9999px;
   left: -9999px;
   /* For mobile, it's typically better to position checkbox on top of clickable
      area and turn opacity to 0 instead. */

/* Default State */
div {
   background: green;
   width: 400px;
   height: 100px;
   line-height: 100px;
   color: white;
   text-align: center;

/* Toggled State */
input[type=checkbox]:checked ~ div {
   background: red;

View Demo

So you can style an element completely differently depending on the state of that checkbox, which you don't even see. Pretty neat. Let's look at a bunch of things the "Checkbox Hack" can do.

Disclaimer: Some of this stuff crosses the line of what you "should" do with CSS and introduces some bad semantics. It's still wicked fun to play with and cool that it's possible, but in general functional behavior should be controlled by JavaScript.

Custom Designed Radio Buttons and Checkboxes

Hide the default UI of a radio button or checkbox, and display a custom version right on top of it. Probably not generally good practice, as users are familiar with default form elements and how they work. But can be good for enforcing cross browser consistency or in situations where the UI is so obvious anyway it's just for fun.

File system like "tree menu"

Demo by Ryan Seddon

Tabbed Areas

The "tabs" design pattern is just toggling on and off of areas, perfect for the checkbox hack. But instead of checkboxes, in which any checkbox can be on or off independently of one another, these tabs use radio buttons in which only one per group can be on at a time (like how only one tab can be active at a time).

Functional CSS tabs revisited

Dropdown Menus

Original by paullferguson and then Forked for betterness by me

Push Toggles

From What's My MPG?

Options from Dabblet

FAQ Answer Revealing

View Demo


A couple demos by Gene Locklin:


  1. The “FAQ Answer Revealing” example is the perfect use case for the HTML5 details/summary elements.

    • Heck yeah, and with somebody’s jQuery fallback that’s a great choice.

    • Tim Molendijk
      Permalink to comment#

      @Mathias: My initial thought was the same. But then on second though; I don’t think so. How would that work? For browsers that support details/summary, no such thing is needed. For all other browsers, we would need a polyfill script anyway. I’m assuming you’re not suggesting that we manually include a checkbox alongside every details/summary instance, as that would be the worst of both worlds.

  2. tom
    Permalink to comment#

    This isn’t practical and pretty much teaches you how to duct tape your way through web development.

    Please put the disclaimer towards the top.

  3. Rafał Krupiński
    Permalink to comment#

    Nice trick indeed, but semantics suffer :)

    • More suffering: I just used it to make a color picker for a custom blog editor I just made for me. It works beautifully everywhere except iOS. How do I fix it?

  4. Justin Lee
    Permalink to comment#

    Like your reference to Soul Coughing in there! Nice!

  5. Maksim Chemerisuk
    Permalink to comment#

    I was especially impressed in past by the demo with modal dialogs done by css ninja (take a look here) which uses the same trick.

  6. I made an iOS style on/off toggle using this very technique about a month ago. Worth a check out.

  7. Permalink to comment#

    That’s a very bad idea: to use styles like “left: -9999px”. Once you turn direction to rtl there will be large horizontal scroll. Perhaps, it would be there already if someone with system RTL settings such as hebrew, arabian or hindi visits your site—I haven’t tested this.
    It’s a much better idea to use styles like “position:absolute;clip:rect(0 0 0 0);” (seen in Lea Verou presentation). “visibility:hidden” is also ok if IE browsers aren’t concerned.

    • I used the clip method on Whats My MPG for the radio buttons example featured in this post.

    • Permalink to comment#

      left: -9999px is OK if parent element has overflow: hidden.
      visibility:hidden for checkbox is bad idea because then input.checked > label won`t work in IE7/8

  8. Permalink to comment#

    These are great fun! I’d be really interested to read more on people whose opinion is that even playing around with these hacks is bad for the industry. To me, they just seem like a really fun thinking exercise.

    • Felipe
      Permalink to comment#

      There’s a lot to learn from the CSS part but the HTML part is nonsense: form elements are to be used in forms (*), not in navigation lists, tabs or treelists.

      Simple questions: where’s the form element? Where’s the submit button?
      Advice: if it’s not a form, don’t use form elements.

      The other “problem” is that CSS Tricks is a successful website with many visitors: Chris Coyer deserves this success but on the other part comes “some” responsibility for beginners and not so beginners that are not sure about their skills and knowledge of good practice.
      In my opinion, this is “Stuff you must not do but still can learn from”. Chris added a disclaimer (even huge disclaimers wouldn’t stop few people to copypaste everything they see but it’s their problem) but I still read the title as “This is stuff I can do? OK I’ll do that”. And people like me will complain that “some of this stuff crosses the line” and “some bad semantics” is too vague (I didn’t see good semantics here though, again, there’s a lot to learn from the CSS part).
      If you need tabs or a treelist on your site, use JS to hide and show content and check

      (*) I know that due to limitations in the HTML4.01 recommendation (there isn’t a way to express the fact that an input can be nested in a %block% like p or a fieldset or a fieldstet into a fieldset, etc), many things like a fieldset outside a form are valid but it still doesn’t make sense in a website.

    • Permalink to comment#

      I generally agree that inputs shouldn’t be used outside forms, but there’s nothing wrong with using a <button type="button"> outside a form, so there are certainly situations for everything. It’s a focusable element and, in maybe cases, a more accessible solution for hiding/showing content than current popular methods.

      Your comment was a little harsh and I think you’re taking this proof of concept stuff too seriously.

      The Internet is mostly a bunch of links, forms and text. It’s nice to see something a little different from time to time.

  9. @Felipe Chris did put a disclaimer there, and people who want to learn can do whatever you want. Nowadays forms are used so much that they could hardly be called that anymore. This is even more of a blogpost than a tutorial anyway.
    People can do what they want with what they’ve learned; if you’re working on your own, you technically only have to be as semantic as you need.

  10. Luke
    Permalink to comment#

    Well, I dont see any “hack”…
    thats pure css :) Stuff in it I use almost every day…

  11. For me, the disclaimer is right in the title. “Hack”. Chris makes it plainly clear that this is something that will gain you an even deeper understanding of the canvas you’re painting on and, due to its non-semantics should really only be implemented in the funnest of cases (or those that relate to us as web manipulators, like Dabblet).

    Nice writeup Chris!

  12. Arto
    Permalink to comment#

    You should add user-select: none; for label to prevent selecting text in buttons and to improve usability.


    -webkit-user-select: none;
    	-khtml-user-select: none;
    	-moz-user-select: none;
    	-o-user-select: none;
    	user-select: none;
    • Art
      Permalink to comment#

      Was Just about to say the same thing (yes, I’m a different person than Arto, just a coincidentally similar name). If I remember right, though, you still end up with a situation where clicking on the label quickly will not register every click as toggling the associated checkbox.

      In light of this, I typically have to use JavaScript and a non-label trigger with two-way state change (clicking the non-label trigger updates the checkbox state, changing the checkbox state updates the trigger’s appearance). Combined with leaving the checkbox positioned off the page but not visible, this still leaves the checkbox accessible via keyboard shortcuts (which is always a concern when replacing an existing element).

      I’d love to see a version of this hack work with rapid-succession clicks on the label, though. The JavaScript for the above isn’t exactly simple.

  13. Ray
    Permalink to comment#

    What is the ‘~’ mean in this line:
    input[type=checkbox]:checked ~ div { … }

  14. Art
    Permalink to comment#

    Verified the above. Even with the user-select on the label and the input, clicking rapidly on the label does not register every click.

    • Art
      Permalink to comment#

      This was tested in Firefox 8, but I know I’ve witnessed the same behavior in other browsers.

  15. Clatan
    Permalink to comment#

    Why should I obey semantics? Isn’t it better to use what is available to make the best possible solution. (optimization, duplication, readability, etc)

  16. Permalink to comment#

    Cant seem to get the tabs to work if they are nested. Does anyone have any ideas how to fix this problem?

    • Works for me on Safari 5.1.2 and Firefox 8.0.1.
      One thing that I noticed the last time I played around though was that on Safari, any selector with more than one combinator didn’t work. Firefox would accept two combinators, but nothing beyond that. So for example :checked + div p would not match the p element in Safari.

  17. I made a lion css ui kit recently and used the checked state for the source list so radio buttons track the selected item and checkboxes handle the treeview.

    Writing the html for a tree menu by hand gets laborous pretty quickly. For segmented push toggles the technique does feel kinda right though.

  18. Drake
    Permalink to comment#

    While I agree most of these examples probably shouldn’t be used in practice, I think the “Push Toggle” examples are actually a perfect use case for this technique. Semantically, those are radio buttons, so it makes sense to have actual radio buttons in your markup. You could fall back to plain old radio buttons w/ labels in unsupported browsers.

    I’ve accomplished the same effect using extra markup for the styled toggles and javascript to manage the state of the hidden radio buttons, but this approach is much cleaner.

  19. Permalink to comment#

    To make it work in IE7/8 you can add a little jquery script:

    if ($.browser.msie && parseInt($.browser.version) < 9) {
      $('input[type="checkbox"]').change(function() {
        $(this).toggleClass('checked', this.checked).parent().hide().show();
      }).each(function() {
        $(this).toggleClass('checked', this.checked).parent().hide().show();

    and just copy style and relpace :checked with .checked:

    input[type="checkbox"]:checked + label { ... }
    input[type="checkbox"].checked + label { ... }
  20. Adam
    Permalink to comment#

    Would be great if as part of the title area for all articles there was a little sub-header similar to the author one, or an icon or similar to classify what versions of html and css are being targeted in the article (eg. HTML5 & CSS3 or HTML4 & CSS3 etc).

    Unfortunately for some of us (maybe just me) its not immediately obvious what version is being targeted.

  21. Permalink to comment#

    Really great and helpful article. Hacks always for shortcut but that always work and became long time solution. I found nice article about CSS hacks.

  22. Permalink to comment#

    Does anyone know if it would be possible to apply some transitions to the tabs using only css. Im trying to make the tabs fade in and out. Ive tried a couple of things and I cant seem to get it to work.

  23. Permalink to comment#

    i like the comment style how can i have this style on my site

  24. Hilde
    Permalink to comment#

    If anyone is having trouble applying label-clicks in iOS, try adding an empty onclick=”” to a parent element (ie the <form>)…

  25. Thanks for pointing out the usecases of input@type=checkbox besides the obvious.

    I once wrote a post about how to rotate images using checkboxes to controll which image is currently active.

  26. Tom Elders
    Permalink to comment#

    I thought inputs nested in labels was the way to go now

        <input type="checkbox" />
        My label text here...

    And off the top of my head, this might do away with the need for the other extra div….?

  27. Bari
    Permalink to comment#

    I have a problem to this in IE 8….
    Does someone know how to make it work in this Evil browser??

    I have This code:
    position: absolute;
    left: -9999px;
    overflow: hidden;

    input[type=checkbox].checked ~ div {
    background-position: 5% -19px;
    background-color: #E5F6FF;
    border: 1px solid #D1EFFF;


    The IE 8 ignores it while all other borwser (chrome,FF,Safari and Opera) handle it easily.

    what should i do?

  28. Daniel
    Permalink to comment#

    Is there any way to prevent the page from jumping when a user selects the label? If there is any amount of scrolling required to get to the label, clicking it will make the page jump to the top.

    Any ideas as to how to get around this?

  29. Permalink to comment#

    It’s also helpful to note that putting the label before the checkbox doesn’t allow it to be styled, since CSS cannot traverse up the DOM (only down) AFAIK. Something like this:

        <label for="toggle">Toggle</label>
        <input type="checkbox" id="toggle">
        input[type="checkbox"]:checked ~ label {
            background: red;

    Will not work, but the exact same code with the two HTML lines reversed will work fine. This is because the label element doesn’t exist for the CSS :checked selector.

    Since the ~ selector only has access to immediate siblings, nesting the checkbox inside a label also will not work, unless the target is inside the label as well.

    Very useful post, I’m using it for a responsive fly-down navigation pane for mobile devices which seems to be working great!

  30. Awal

    Why not just use display:none for the input.checkbox element? Why do we have to push it across the page?

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".