Quick Tip: CSS Only Dropdowns With The Checkbox Hack

Download

In this quick tip, we are going to take a look at a CSS only technique for creating dropdowns. It revolves around the checkbox HTML element and some smart usage of CSS selectors without a single line of JavaScript.

You can see the example in action in our editor. Click the "Edit" button to see the code. You can download the full source code from the button above.

<div class="dropdown">
    <input type="checkbox" id="checkbox-toggle">
    <label for="checkbox-toggle">Click to Expand</label>
    <ul>
        <li><a href="#">Link One</a></li>
        <li><a href="#">Link Two</a></li>
        <li><a href="#">Link Three</a></li>
        <li><a href="#">Link Four</a></li>
    </ul>
</div>
body{
    text-align: center;
    font: 16px/1.5 sans-serif;
    padding-top: 40px;
    background-color: #ECEFF1;
}

.dropdown{
    position: relative;
    display: inline-block;
    font-size: 16px;
    color: #FFF;
}

/**
    Hide the checkbox itself. Checking and unchecking 
    it we will be done via the label element.
*/

input[type=checkbox]{
    display: none;
}

/* Click to expand button */

label{
    box-sizing: border-box;
    display: inline-block;
    width: 100%;
    background-color: #57A0D4;
    padding: 15px 20px;

    cursor: pointer;
    text-align: center;
    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);

    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

/*  The ul will have display:none by default */

ul{
    position: absolute;
    list-style: none;
    text-align: left;
    width: 100%;
    z-index: 1;
    margin:0;
    padding:0;
    box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2);

    display: none;
}

ul li{
    padding: 15px;
    background-color: #fff;
    color: #4FB9A7;
    margin-bottom: 1px;
    cursor: pointer;
}

ul li:hover{
    background-color: #4FB9A7;
    color: #FFF;
}

ul li a{
    color: inherit;
    text-decoration: none;
}

/**

    By using the Following-sibling selector (~),
    we can target elements positioned after our checkbox in the DOM tree.

    With the state pseudo selector (:checked),
    we can make changes depending on the state of the checkbox.

    Using this combination of selectors
    allows to change the color of the label
    and show the list of items
    only when the checkbox is checked.

*/

input[type=checkbox]:checked ~ label {
    background-color: #3D88BD;
}

input[type=checkbox]:checked ~ ul {
    display: block;
}

The Markup

This is what our HTML structure looks like. Here it's important to note, that the input element has to come first, before the label and before the ul. You will understand why this is needed later, when we check out the CSS.

<div class="dropdown">
    <input type="checkbox" id="checkbox_toggle">
    <label for="checkbox_toggle">Click to Expand</label>
    <ul>
        <li><a href="#">Link One</a></li>
        <li><a href="#">Link Two</a></li>
        <li><a href="#">Link Three</a></li>
        <li><a href="#">Link Four</a></li>
    </ul>
</div>

As you can see, there's nothing out of the ordinary here, all of elements are in standard, frequently used HTML:

  • The div will serve as a container for the whole thing.
  • The input type=checkbox is needed because of it's checked/unchecked property. It will be hidden at all times
  • The label will be used to toggle the input and will also serve as the trigger for your dropdown.
  • The ul is simply a list we want to be visible when the dropdown is extended. Can be any other element.

The Checkbox Hack

We only need the checkbox element for its checked state, which we can style using the :checked CSS pseudo selector. Checking and unchecking it is done by clicking the label, which is a basic browser feature. We first hide the checkbox:

input[type=checkbox]{
    display: none;
}

We also hide the UL by default - it is the dropdown menu which should only be visible when the dropdown is extended.

ul{
    display: none;
}

And here is the hack. If we combine the :checked selector with the general siblings selector (~) we can change CSS properties of elements that follow the checkbox. This is the reason we needed the checkbox to come in first, before the ul in the DOM tree.

input[type=checkbox]:checked ~ ul {
    display: block
}

The CSS snippet above will show the unordered list only when the checkbox is checked. Since the possible status of an input of type checkbox is binary, it is perfect for toggling the items between expanded and hidden state.

Hope you enjoyed our quick tip!

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles

Comments 18

Danny Markov

However we should note that there are some disadvantages to using this technique to build dropdowns - the menu is not hidden automatically when you click outside the dropdown, and if you have a long list of elements, it is not scrolled so that it fits the screen. For long lists of items a <select> is still better. But if you need a menu that is highly customizable you can go ahead and use this technique.

Use button and focus instead of checkbox and active ;)

Mr Smartman

That's why you should use "focus" instead of "checked" then it works :D

:focus works in Chrome more generic rather than in FF you just can use it for input elements and for some reason I also couldn't made it work with a checkbox

Michele Nasti

It would be great if you put even some kind of animation to it. I am a total newbie of css animations so a tutorial on them would be great!

You could animate the the max-height of the ul-element.

Here's a fiddle:
https://jsfiddle.net/4y50uarb/

Roman Nichepurenko

Why not use "transition + opacity" instead of "display"? It's looking better.

Alessandro Guarita

It really looks better, but with the opacity the element is still there and you can click on the links, even if they are invisible (and you can't click on an eventual link or in a form that appears behind the dropdown).
To make the opacity works you need put pointer-events: none on the drop-down hidden state and pointer-events: auto when it appears

Danny Markov

This was my original idea as well, but as it turns out, pointer events don't have full browser support.

https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events#Browser_compatibility

Firefox supports pointer events, but doesn't have them available by default. However, they were added to the most recent Nightly build, so we should be seeing more from them soon.

https://hacks.mozilla.org/2015/08/pointer-events-now-in-firefox-nightly/

If you do a combination of visibility and transition-delay, then you can animate however you want and the visibility: hidden will prevent pointer events until activated.

Here's a demo with regular dropdowns

Awesome! One more thing, how can i close the dropdown after click on an item?

Danny Markov

My best guess for now is to go with JavaScript.

If I think of a CSS solution I'll let you know.

You can quickly achieve this by making <label> tab wrap the checkbox & ul elements

<label>
<input type="checkbox">
<ul>
...
</ul>
</label>

cus click on menu item will consider as uncheck the checkbox

Sebastian

Keep an eye on older Webkits (Build 534.3 - Android Stock 4.0–4.3 and older Safari).
There are issues with sibling and pseudo selectors.
http://css-tricks.com/webkit-sibling-bug/

schadeck

You can get this to work if you leverage button:focus element and a sibling selector to activate a transition: examplet

Use radio inputs instead of ul/li so you don't have to use javascript.
here is a codepen i made as an example

The ~ sibling selector has low compatibility. You should wrap the "label" and the "ul" in a div so you can reach both of them with + selector.

Example: "input[type=checkbox]:checked + div label" (to give active style to the label) and "input[type=checkbox]:checked + div ul" to make the options appear.

This way you gain more compatibility in older devices.

If I use Twitter bootstrap library's dropdown menu, the shown menu is hidden back when you press any other part of the page. How would you hide shown menu with this css only hack?