DEV Community

Jacob Paris
Jacob Paris

Posted on

Hamburger Menu in Pure CSS

A user on Twitter built a sliding hamburger style menu in HTML and javascript and asked for tips. I built one here in pure CSS using a checkbox as the trigger and CSS Transforms to animate the menu in and out.

https://codepen.io/anon/pen/YbKvgR?editors=1100

<input id="sidebar__trigger" class="sidebar__trigger" type="checkbox" />
<label for="sidebar__trigger">
  <span class="sidebar__button sidebar__button-open">OPEN</span>
  <span class="sidebar__button sidebar__button-close">NOPE</span>
</label>
Enter fullscreen mode Exit fullscreen mode

So this input is our trigger to open and close the menu. Since it's a checkbox, it already has the toggle functionality built in by the browser. We need to give it an ID so that we can link a label to it. Once we've attached a label we can toggle the checkbox simply by clicking on the label.

Now you may be thinking that we can clean this code up by placing the input inside the label -- and normally you would be correct. If an input is the only input inside a label element, the browser will know they are paired together and you don't need to specify the id on the input or the for on the label. In this case, we are going to use CSS to toggle the menu based on the checkbox state and for that to work the menu must be a sibling downstream of the input. If the input was inside the label, the menu would also need to be inside the label, and then any time you click the sidebar it would toggle in or out. Useful in some cases perhaps, but not for us.

If the double underscore __ naming is unfamiliar, this is a design pattern called BEM which stands for Block__Element--Modifier. By using this naming convention we can ensure that none of our CSS is going to interfere with any other code on the site. A generic class like .input is very likely to be used somewhere else, and that would cause adverse results.

We have two <span> elements inside the label. Each of these has a different class -open and -close which we will later reference with CSS to toggle based on the checkbox state.

<ul class="sidebar">
    <li>Home Page</li>
    <li>Example 1</li>
    <li>Example 2</li>
    <li>Example 3</li>
    <ul>
        <li>Example 1</li>
        <li>Example 2</li>
        <li>Example 3</li>
    </ul>
    <li>Example 1</li>
    <li>Example 2</li>
    <li>Example 3</li>
</ul>
Enter fullscreen mode Exit fullscreen mode

This is just a generic placeholder menu. The only important part is that it has the class .sidebar because that's how we're going to refer to it in our CSS. It also must be placed below the input in source order.

.sidebar {
  background: #333;
  color: white;
  max-width: 200px;
  transition: transform 0.5s;
}
Enter fullscreen mode Exit fullscreen mode

We'll give the sidebar a basic styling so we can tell the difference between it and the rest of the page. Important here is the transition means every time the transform property is changed, it will gradually ease from the old value to the new one over 0.5 seconds. Increase this number to make the sidebar slide out slower, or decrease it to speed it up.

.sidebar__trigger {
    display: none;
}
Enter fullscreen mode Exit fullscreen mode

We want to hide our checkbox so that we can see our fancy labels instead.

.sidebar__trigger:checked + label > .sidebar__button-open {
    display: none;
}
Enter fullscreen mode Exit fullscreen mode

We'll break this down piece by piece

.sidebar__trigger:checked // Find our input only when checked
+ label // Move to a label element that is an immediate next sibling
> .sidebar__button-open // And select the open-button that is an immediate child
Enter fullscreen mode Exit fullscreen mode

The display: none; simply hides that element. Then we do the same thing for the close-button when the input is not checked.

.sidebar__trigger:not(:checked) + label > .sidebar__button-close {
    display: none;
}
Enter fullscreen mode Exit fullscreen mode

The only new thing here is the :not() selector, which is essentially a negative match.

.sidebar__trigger:not(:checked) ~ .sidebar {
    transform: translateX(-100%);
}
Enter fullscreen mode Exit fullscreen mode

The previous rule used + label which selects only a label tag that is an immediate next sibling. This rule uses ~ .sidebar which selects any later sibling. Now that we've selected the sidebar, we can move its position with a CSS transform to move it left 100% of its own width. This will obscure it off-screen until the input is checked which cancels this rule and brings it back to its original position.

.sidebar__button {
    width: 300px;
    border: 1px solid #ddf;
    padding: 1rem;
    border-radius: 0.25rem;
}

Enter fullscreen mode Exit fullscreen mode

This is just a little styling for the button to make it look like a button and not just some text.

A working example is shown here at this Codepen

https://codepen.io/anon/pen/YbKvgR?editors=1100

Top comments (1)

Collapse
 
spyrosko profile image
spyrosko

You beautiful man. That works perfectly and so concise. Really cool. Thank you.