Pure CSS Drawer Menu

Unless you've been living under a rock for the last 4 years, you know about the "drawer menu" navigation style. It was popularized by the Facebook app a few years back, and has since made it into the standard toolbox of almost every web designer out there. Let's make one without Javascript, just for the heck of it!

A quick heads up, in case you missed it in the subtext: this is a fairly advanced walkthrough. The CSS we're using is well supported, but not fully; if you want your design to look great in a browser that was released more than 3 years ago, you may want to look to other solutions. This is a proof of concept. You've been warned!

Still here? Great! Let's get started.

The Concept

Basically, with drawer menus you need to view your website like a multi-paned window. Each element is its own pane, and the content of each pane is independent from the others. This allows each to be scrolled separately, and manipulated depending on whether our drawer menu is open or closed. One important caveat of this approach is that when you scroll, you are never scrolling the body of your page; just an individual pane. There are benefits and drawbacks to this, as there are with any more complex layouts.

Here's an illustration to help. The container box is the body of our html. It stays a constant size - namely, the height and width of your browser window. It never overflows the window.

The blue box is your page content. When the menu is closed, it is the same exact size as the body of your html, and it can scroll vertically. The change happens when your menu is opened; the content then gets pushed to the right and overflows the body to make room for the open menu.

The green box is your menu. It can change width when opened, but never overflows the body either. However, its content can scroll vertically as well.

The blue and green boxes scroll independent of each other, and only interact directly when the menu is being opened or closed.

That clear as mud? Ok—moving on!

The Execution

Now that we've got the basic concept under our belts, let's begin the actual code by looking at our HTML. While this is by far the easier part of our tutorial, there are some very important aspects that must be paid attention to. Here's the code:

html
<body>
   <input type="checkbox" id="drawer-toggle" name="drawer-toggle"/>
   <label for="drawer-toggle" id="drawer-toggle-label"></label>
   <header>Header</header>
   <nav id="drawer">
      <ul>
         <li><a href="#">Menu Item</a></li>
         <li><a href="#">Menu Item</a></li>
         <li><a href="#">Menu Item</a></li>
         <li><a href="#">Menu Item</a></li>
      </ul>
   </nav>
   <div id="page-content">
      <p>Page Content</p>
   </div>
</body>

See the Pen f779d8b80d986ae5bb5dd4aa816605aa by Jesse Couch (@designcouch) on CodePen.

Notice that I've only bothered to include the body part of your html. Obviously, you're going to need to include the other important tags as well (but that should be a given).

Important Stuff

The order of the elements is not too important, but it is paramount that the #drawer-toggle input is above everything else. This is important because this input drives the menu states (open and closed) using the general sibling CSS selector. While this selector will target any matching siblings, it won't go backwards through your HTML to find them.

Now that we've got the HTML covered, let's move on to the CSS. This is where things get interesting (and fun!).

css
* {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  -o-box-sizing: border-box;
  box-sizing: border-box;
  /* adds animation for all transitions */
  
  -webkit-transition: .25s ease-in-out;
  -moz-transition: .25s ease-in-out;
  -o-transition: .25s ease-in-out;
  transition: .25s ease-in-out;
  margin: 0;
  padding: 0;
  -webkit-text-size-adjust: none;
}
/* Makes sure that everything is 100% height */

html,
body {
  height: 100%;
  overflow: hidden;
}
/* gets the actual input out of the way; 
we're going to style the label instead */

#drawer-toggle {
  position: absolute;
  opacity: 0;
}

See the Pen 0dd46b4e76862b6a3af0e00a93b53f73 by Jesse Couch (@designcouch) on CodePen.

Ok - do you see what I did there? Basically, we're using the universal selector to add box-sizing to everything, animations to all the transitions and a few other miscellaneous things. Also, since we're using the checkbox hack, we're removing the actual input from the flow by giving it absolute positioning and making it transparent.

css
#drawer-toggle-label {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  left: 0px;
  height: 50px;
  width: 50px;
  display: block;
  position: fixed;
  background: rgba(255, 255, 255, .0);
  z-index: 1;
}
/* adds our "hamburger" menu icon */

#drawer-toggle-label:before {
  content: '';
  display: block;
  position: absolute;
  height: 2px;
  width: 24px;
  background: #8d8d8d;
  left: 13px;
  top: 18px;
  box-shadow: 0 6px 0 #8d8d8d, 0 12px 0 #8d8d8d;
}

header {
  width: 100%;
  position: fixed;
  left: 0px;
  background: #efefef;
  padding: 10px 10px 10px 50px;
  font-size: 30px;
  line-height: 30px;
  z-index: 0;
}

See the Pen 5b50cc8f3ce7c6b0deda59eeda5b82e5 by Jesse Couch (@designcouch) on CodePen.

Cool. What we've done here is add our top navigation. This consists of the menu toggle and the page header. The menu toggle is the styled label for our checkbox, and not the checkbox itself (since you can't reliably style checkbox inputs).

We're using the :before pseudo-element to create the "hamburger" menu icon. The top line is the actual pseudo-element, and the bottom two are created using box-shadow.

css
/* drawer menu pane - note the 0px width */

#drawer {
  position: fixed;
  top: 0;
  left: -300px;
  height: 100%;
  width: 300px;
  background: #2f2f2f;
  overflow-x: hidden;
  overflow-y: scroll;
  padding: 20px;
  -webkit-overflow-scrolling: touch;
}
/* actual page content pane */

#page-content {
  margin-left: 0px;
  margin-top: 50px;
  width: 100%;
  height: calc(100% - 50px);
  overflow-x: hidden;
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
  padding: 20px;
}

See the Pen 653106bc080ba20691c85d5a45583201 by Jesse Couch (@designcouch) on CodePen.

Here we go with the actual panes. We've got two; the content pane and the drawer pane. These styles apply to the "closed" menu state, so if you notice, the position of the #drawer is -300px (the negative equivalent of its width) to remove it from view.

css
/* checked styles (menu open state) */

#drawer-toggle:checked ~ #drawer-toggle-label {
  height: 100%;
  width: calc(100% - 300px);
  background: rgba(255, 255, 255, .8);
}

#drawer-toggle:checked ~ #drawer-toggle-label,
#drawer-toggle:checked ~ header {
  left: 300px;
}

#drawer-toggle:checked ~ #drawer {
  left: 0px;
}

#drawer-toggle:checked ~ #page-content {
  margin-left: 300px;
}

See the Pen 854e4c6d5be186ebd2db8566ad7d19e6 by Jesse Couch (@designcouch) on CodePen.

Here's where the magic happens. When the #drawer-toggle input triggers the :checked state, we re-style any of its adjacent sibling elements accordingly. Basically, using a couple of different methods, we shift everything 300px to the right, so that the menu becomes visible.

As another added UI bonus, we're expanding the label to cover the page content, so that a click anywhere outside of the open menu closes it again; it also adds a semi-transparent overlay to bring more focus to the menu

css
/* checked styles (menu open state) */

#drawer-toggle:checked ~ #drawer-toggle-label {
  height: 100%;
  width: calc(100% - 300px);
  background: rgba(255, 255, 255, .8);
}

#drawer-toggle:checked ~ #drawer-toggle-label,
#drawer-toggle:checked ~ header {
  left: 300px;
}

#drawer-toggle:checked ~ #drawer {
  left: 0px;
}

#drawer-toggle:checked ~ #page-content {
  margin-left: 300px;
}

See the Pen 854e4c6d5be186ebd2db8566ad7d19e6 by Jesse Couch (@designcouch) on CodePen.

These just add a few responsive tweaks and the styles for the drawer menu links. Nothing too special.

In Conclusion

Well, that's really about it. A semi-challenging little walk-through, but a lot of fun to tackle, if you ask me. As always, feel free to address any questions you have in the comments below—I'll answer them to the best of my ability!

demo

See the Pen f779d8b80d986ae5bb5dd4aa816605aa by Jesse Couch (@designcouch) on CodePen.

Did I help you out?

Read More

←  Playing with Hamburger Icon Animations Time for a Free Logo  →

There are 27 comments. Add Yours.

A Note from the Moderator

Thanks for taking the time to comment! I'll respond as quickly as possible if necessary. In the meantime, please keep the following in mind:

  • All comments must be appropriate. I'll delete 'em if you get nasty.
  • Please allow for response time. I'm here as much as possible, but can't always respond as quickly as some would like.
  • Please stay on topic.
  • I don't work for free—if you want custom work, feel free to get in touch and I'll write up a quote!
  1. Ramond

    Ramond

    Apr 28, 2014

    This is LEGIT. While JS isn't necessarily a bad thing, it's always nice to reduce overhead. Thanks, Jesse!

    Reply to Ramond

    1. Jesse

      Jesse

      Apr 20, 2015

      Thanks Ramond :)

  2. Syd

    Syd

    Oct 14, 2014

    Excellent tutorial, very clear and well explained. One downside; I can't get the menu to work using the keyboard, only with the mouse - any thoughts?

    Reply to Syd

    1. Jesse

      Jesse

      Oct 14, 2014

      Thanks, Syd. Glad you enjoyed it. In order to get keyboard commands onboard, I think that a JS trigger would probably be in order. That kind of eliminates the point of using a checkbox, as JS can be just as easily used to trigger an "open" class on our menu. If you're interested in knowing how to do this and don't know where to start, I can provide some basic pointers.

  3. Louis DeScioli

    Louis DeScioli

    Oct 19, 2014

    This article is very clear and it does get the job done, but since you alter the position directly you don't get the buttery smoothness you're looking for. The browser has to recalculate the page layout and this is a costly computation.

    Instead of changing the position, use CSS transforms. This article (http://tympanus.net/codrops/2014/09/16/off-canvas-menu-effects) has some beautiful examples and includes code. Done in 99% CSS, they just don't use checkboxes for the toggles.

    Reply to Louis

    1. Jesse

      Jesse

      Oct 22, 2014

      Great thoughts, Louis! Transforms do indeed accomplish a smoother transition, with less overhead.

  4. krish

    krish

    Oct 20, 2014

    How to add the sub menus to the menu and when I resize how to make it scroll and also submenu shows??

    Reply to krish

    1. Jesse

      Jesse

      Oct 22, 2014

      I would add nested lists to add submenus. These are not accounted for in the provided css, but here's a decent pen of mine on Codepen of what I'm talking about: http://codepen.io/designcouch/pen/sCtAp

  5. Krish

    Krish

    Oct 22, 2014

    I dint see the submenus working in that codepen link. I have a issue with the scrolling..when I give overflow-y scroll. I am not seeing the submenus

    Reply to Krish

  6. Krishna

    Krishna

    Oct 22, 2014

    How to apply scroll to the left navigation?

    Reply to Krishna

  7. Robert

    Robert

    May 29, 2015

    Since you're using box shadows for the hamburger icon.. would there still be a way to use animated icons with css animations in this case? I don't see why not? Correct me if I'm wrong :)

    Great Blog, love the articles, very well designed.

    Reply to Robert

    1. Jesse

      Jesse

      May 29, 2015

      Thanks Robert! I'm generally pretty happy with my new design.

      As to using animations with box-shadow, that's a little tricky. Since two of the lines are technically shadows of the first, they behave exactly the same as the first when a transform is applied. In order to do animations, each line would need to be its own block element. I did a few exercises to this effect in another one of my posts: http://www.designcouch.com/home/why/2014/06/23/playing-with-hamburger-icon-animations/

      Hope this helps!

  8. Sam

    Sam

    Aug 26, 2015

    I really like your approach. Brilliant!! How do I place the menu to the right of PAGE CONTENT. Opposite to what you have done? Thanks.

    Reply to Sam

    1. Jesse

      Jesse

      Sep 21, 2015

      Since the #drawer container div has its position fixed, you simply change the "left" part of its CSS to "right". Boom.

  9. M.Areeb

    M.Areeb

    Sep 15, 2015

    Thankyou so much jesse it's really very simple...

    Reply to M.Areeb

  10. Shawn

    Shawn

    Sep 21, 2015

    I want to thank you for the work Jesse. Nice stuff here. While I'm only a front-end code hack (more on the UX/visual design side), I have done my best to implement the Pure Drawer feature on my site. So far, I think its working well (I plan to polish up some of my own HTML a little later). I have run into a lil snag though. I'd like to incorporate some internal named anchor links but having trouble finding a solution that works well with Pure Drawer. Most of the JQuery/JS solutions I've investigated so far do not work on a page where I have Pure Drawer setup. I was curious if you could comment on how you would handle that... Again, many thanks!

    Reply to Shawn

    1. Jesse

      Jesse

      Sep 21, 2015

      Thanks, Shawn! Glad you found it of use. I'm afraid that you're going to have to expand on this a bit. I'm not entirely clear what you mean when you say "Named Anchor Links". This solution does not use anchors to drive it; simply a checkbox and its corresponding label. Unsure what jquery/js solutions you're trying, but this technique should not interfere whatsoever with any jquery/js on the page. Quite the contrary - as it's completely removed from any js-driven interactions/reactions, you should be able to do whatever you like without issue. If you're seeing issues, I would suspect that the culprit is somewhere in either the JS or in your version of the code I wrote. Again, with more detail I can definitely attempt a better response. Cheers!

    2. Shawn

      Shawn

      Sep 21, 2015

      Sorry for the confusion. I could have been more clear. Let me try again...

      Pure Drawer is working great! So no problems with getting it implemented using the code snippets and instructions you have provided. Again.. thanks very much!

      However, I have a couple pages that I would like to add internal anchors (jump to a position on the same page as anchor with smooth scrolling animation) to. I did some research and found a few options. Here's one of those: https://github.com/JamyGolden/PlusAnchor

      When I try to implement, I can get animated anchor jumping to work on pages without Pure Drawer - but not on pages with Pure Drawer.

      I realize you're probably pretty busy so I don't mean to take up too much of your time. Any help or guidance is greatly appreciated.

    3. Shawn Kelshaw

      Shawn Kelshaw

      Sep 24, 2015

      I've gone back and reread some of your overview instructions and I think I've got some ideas on how to achieve my goals.

  11. Steph

    Steph

    Sep 30, 2015

    Hey Jesse, what a lovely solution! And this tut was written so well, it was a breeze to implement. I've added this to an existing site, and it works beautifully everywhere but Chrome for iOS. I'm stumped as to why and wonder if you have any ideas? (I'm sorry if that's a silly question; I'm just branching out from my comfy desktop into the world of mobile...)

    Reply to Steph

    1. Jesse

      Jesse

      Oct 05, 2015

      Steph, I'm afraid I'm unable to shed too much light onto this without a little more information. There are no missing browser prefixes and the technique used here should work in any modern browser. Can you expand on the issues you're seeing in Chrome for iOS?

  12. Deep

    Deep

    Jan 01, 2016

    Nice stuff. Excellent and clear explanation. One small note, for iOS browsers, I believe putting a z-index on the ID "drawer" is in order. Correct me if I'm wrong, but that seems to solve the issue :)

    Reply to Deep

  13. Danny

    Danny

    Feb 29, 2016

    Thanks for the tut.
    But when i open my page on the iPone the hamburger menu doesn't work.

    Do you've any idea what i doing wrong?

    Reply to Danny

  14. sachinkumar

    sachinkumar

    Jun 02, 2016

    very nice drawer

    Reply to sachinkumar

  15. nyro

    nyro

    Aug 11, 2016

    im trying to add a menu bar just under the hearder but it doesnt seeem to work and i want to also know if its possible for me to put the menu bar in the same line as the header

    Reply to nyro

  16. Krishan

    Krishan

    Sep 17, 2017

    Why did we use :before pseudo-element to create the "hamburger" menu icon? Any specific reason?

    Reply to Krishan

    1. Jesse

      Jesse

      Sep 22, 2017

      That's because I believe in using both HTML and CSS as they were originally intended. HTML is structural, while CSS is presentational. Adding an image or other extra elements to the HTML just to make your design look a certain way is contrary to the structural intent of HTML. In HTML, we should simply provide one element. For example:

      This is not correct html structure
      -- hamburger icon
      ----line 1
      ----line 2
      ----line 3

      This is correct html structure
      -- hamburger icon

      Hope this helps you understand, and thanks for your comment!