By Austin Wulf

Pure CSS Off-screen Navigation Menu

By Austin Wulf

Hamburger menu, drawer menu, off-canvas menu: Whatever you call it, hiding a website’s primary navigation just off screen is becoming a ubiquitous pattern in responsive web design. More and more sites feature a fixed-position icon that, when tapped, pushes the entire site to the side to reveal a hidden navigation menu.

While there are plenty of jQuery plugins that will create this effect for you, it’s actually pretty easy to achieve without using any JavaScript at all.

This article will show you how to make a simple version of the off-canvas menu and sliding effect using only CSS.

Before we get started, I’d like to make a note that using this method for creating the drawer menu means your site’s navigation will always be in a drawer, regardless of viewport size. If you only want the drawer menu on smaller screens, you’ll have to use some JavaScript to manipulate the DOM order or use some funky CSS to make the menu look right on larger screens. In other words: It’s pretty and simple, but it’s not always the right solution for every situation.

Here’s a CodePen demo showing the end result:

See the Pen Pure CSS Off-Screen Navigation Menu by SitePoint (@SitePoint) on CodePen.

Start with Some HTML

The markup for our off-canvas menu is a bit different than your standard navigation menu. Instead of sticking it in the site’s header, we’re going to start right inside the <body> tag.

This is the basic structure:

<ul class="navigation">
    <li class="nav-item"><a href="#">Home</a></li>
    <li class="nav-item"><a href="#">Portfolio</a></li>
    <li class="nav-item"><a href="#">About</a></li>
    <li class="nav-item"><a href="#">Blog</a></li>
    <li class="nav-item"><a href="#">Contact</a></li>

<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger"></label>

<div class="site-wrap">
    <!-- insert the rest of your page markup here -->

You can see our site’s markup is made up of three main elements: the navigation, a checkbox and label pair, and the site’s actual content.

A few things to note:

  • The navigation section is first in the source order because it’s “behind” everything else on the site. You can use whatever HTML tags you want to build the navigation. Here I’m using an unordered list, which is common.
  • The trigger to slide out our menu is a checkbox input with a label. Typically the label would come before the input or wrap around the input. In this case, the input has to come directly before the label. We’ll see why later when we add the CSS.
  • The rest of our site has to be wrapped in a unique div. This is so that when we open the menu, everything else can slide slightly off-screen to reveal the hidden navigation elements underneath.

Now that we’ve got our basic HTML structure, we can start making it look pretty!

The CSS for the Menu Items

Let’s start by styling the navigation menu and items. First off, we need to make sure our navigation menu is behind our page content and that it stays in place even if a user scrolls:

.navigation {
    list-style: none;
    background: #111;
    width: 100%;
    height: 100%;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 0;

Next, I’ve added some styles to make our navigation look snazzy (background colors, borders, gradients, etc.). I won’t reproduce the code here, but you can review the demo to check those out.

Now we have some nice looking menu items, but it doesn’t look so great with all of our content just laying on top of it. Let’s add some styling to hide the menu until we’re ready for it.

The CSS for the Site Wrapper

To start, let’s make sure the site’s content completely covers our menu. At this point, you may want to add a few paragraphs of lorem ipsum to your .site-wrap element, if you haven’t already added any content.

.site-wrap {
    min-width: 100%;
    min-height: 100%;
    background-color: #fff;
    position: relative;
    top: 0;
    bottom: 100%;
    left: 0;
    z-index: 1;

Note that we must specify a background on .site-wrap or else the menu will show through. You can, of course, use any kind of background you want. I added the following to mine:

.site-wrap {
    /* ...previous styles here... */
    padding: 4em;
    background-image: linear-gradient(135deg, 
                      rgb(254,255,255) 0%,
                      rgb(221,241,249) 35%,
                      rgb(160,216,239) 100%);
    background-size: 200%;

The CSS for the Menu Trigger

Next we’ll add the styles that change the menu trigger from a standard checkbox input into the classic “hamburger” icon that we all know and love.

First, let’s hide the checkbox.

.nav-trigger {
    position: absolute;
    clip: rect(0, 0, 0, 0);

Editor’s note: Originally, this code was using display: block along with zero width and height for the checkbox, to make it invisible but still accessible. It turns out, this combination was causing iOS to crash the browser when the menu was opened. I’ve changed the technique to use the clip property instead, which seems to have the same level of accessibility.

Here we are hiding the checkbox using the clip property, which requires that the element be set to position: absolute.

Now let’s style the <label> element:

label[for="nav-trigger"] {
    position: fixed;
    top: 15px;
    left: 15px;
    z-index: 2;

First, we set the label to position: fixed so that it stays in the same spot as the user scrolls. The top and left properties dictate how far from the edge of the viewport the icon will sit. We also make sure the trigger’s z-index is at least one higher than that of the .site-wrap element.

Next, we add additional declaratins to make the lable into a “hamburger” icon.

label[for="nav-trigger"] {
    /* ... previous styles here... */
    width: 30px;
    height: 30px;
    cursor: pointer;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='' xmlns:xlink='' version='1.1' x='0px' y='0px' width='30px' height='30px' viewBox='0 0 30 30' enable-background='new 0 0 30 30' xml:space='preserve'><rect width='30' height='6'/><rect y='24' width='30' height='6'/><rect y='12' width='30' height='6'/></svg>");
    background-size: contain;

I’ve used inline SVG as a background image, but you can use any icon you want, including :before and :after pseudo elements to recreate the “hamburger” icon using pure CSS.

Notice I’ve also included cursor: pointer; to visually indicate interactivity with cursor-based input.

The CSS to Make the Trigger Work

Now that our menu, site wrapper, and trigger are all styled, let’s add the last few lines of CSS that make it all work.

.nav-trigger:checked + label {
    left: 215px;

.nav-trigger:checked ~ .site-wrap {
    left: 200px;
    box-shadow: 0 0 5px 5px rgba(0,0,0,0.5);

The second declaration block above ensures that the site wrapper is pushed to the right by 200 pixels. I also added a box shadow to the site wrapper to give it that extra visual feel of being stacked on top of the menu.

The first selector (.nav-trigger:checked + label) controls the position of the trigger when the menu is open. You’ll want to add the number we used earlier on label[for="nav-trigger"] to the amount you want the site wrapper to slide out. So in this case: 15px + 200px = 215px.

This is where the source order of the trigger elements becomes important. The second selector uses ~, the general sibling selector, to target .site-wrap when .nav-trigger is checked. The source order of our checkbox input isn’t as important here.

However, we have to target both .site-wrap and our <label> element based on whether or not our checkbox input is checked. To accomplish this, we use the + (adjacent sibling selector) to target the <label> element that’s next to the checked checkbox. If we put the label first, there’s no way to move it along with the site wrapper when we activate our trigger.

As a finishing touch, we can add a CSS transition to both the trigger and the site wrapper to open the menu with a smooth animation. Make sure to include any relevant browser-prefixed attributes in your version, or else use something like Autoprefixer.

.nav-trigger + label, .site-wrap {
    transition: left 0.2s;

One last thing: Make sure to hide any overflow on the x-axis of your <body>. Without this, your users will be able to scroll the whole window left and right when the menu is open.

body {
    overflow-x: hidden;

The Finished Product

And that’s it! We’ve successfully built a slick off-screen navigation menu without any JavaScript. Again, here’s the CodePen to demonstrate what it looks like when it all comes together:

See the Pen Pure CSS Off-Screen Navigation Menu by SitePoint (@SitePoint) on CodePen.

Now that you know how it’s done, feel free to play around with the idea. Make a version that slides in from the right, or make one that has both a left and right menu.

I’d love to see what you can come up with, so share a CodePen of your own design in the comments, or links to other examples of pure CSS off-screen navigation menus.

  • Nice work on this. I published a similar technique a couple months ago (not saying I invented the technique mind you) based off codrops morphing menu button tutorial:

    in mine I used radio buttons instead of checkboxes so that clicking the blacked out content area, as well as the X button, could be used to close the sidebar nav.

    Again, nice work. I liked the way you wrote up the tutorial. It’s much easier to parse with the smaller code blocks than mine.

  • Jingqi Xie

    Although it requires more in DOM than the JavaScript method, it’s still worth trying.

  • Would the off-canvas bit be better to layer over the current page, as opposed to push it aside? Would that be less expensive in terms of pixels / redraw on a less powerful device?

    • This is a good question, and I think you’re right: It should take less work to redraw the menu overlaying the canvas instead of pushing the canvas aside.

      The reason I chose this animation specifically was because it’s a common pattern on native mobile apps, but I have done the “overlay” method on websites in the past. I haven’t tried the checkbox trigger with that animation style, but I have a feeling it could still be used there, as well.

      • Moi? Even with performance issues aside, I prefer an overlay. I personally don’t find shifting the whole main screen a smooth UX. Let the known stay known and layer on top without disruption. Of course there are exceptions.

        Do you know / think the above could be adjusted for overlay? If so, care to add that? Maybe? Please?

        • LouisLazaris

          If I get a chance, I’ll see if I can fork it to try the overlay method, but feel free to do it yourself and post it here! :)

    • mystrdat

      A dramatic difference actually, but then it’s a different effect.

    • Ooh. I had this on my TOREAD list:

  • .navigation {

  • Aaron Moreno

    This is cool, thanks for great sample. Going to use this for some mockups and prototype work.

  • Mate Brkić

    Nice work.
    TIP: Try to make this accessible to keyboard if it is possible. I didn’t read tutorial yet but I tried demo and no luck with my keyboard.

  • Theracoon

    Works OK on My HTC One in Chrome but Crashed an iPhone 4S Safari both times I tried it. I was curious as I tried a similar method about 2 years ago but couldn’t get the iPhone to redraw sibling elements based on a targeted element.

    • LouisLazaris

      Hi @Theracoon:disqus,

      Thanks for pointing this out. I actually noticed this myself prior to your comment but I was trying to set aside some time to debug this before I posted a comment about it.

      It turns out, any browser on iOS was crashing (they all run on the same engine, even Chrome on iOS), because of the checkbox being given height and width values of “0”. I’ve changed the method for hiding the checkbox to now use position: absolute along with the clip property, which is supposed to be an accessible way to hide elements.

      So the crashing should no longer happen on iOS (iPhone or iPad) anymore. Thanks!

      • Theracoon

        Excellent, I’ll give it a try.

  • This is a great way to handle long menus with many items or short screens that can’t hold a handful of items. Thanks for the suggestion!

    • Pete Helgren

      Does this still work? The codepen reference doesn’t add scroll to the navigation menu. Tested with latest Firefox and Chrome.

      • Expatriate

        Works perfectly for me in latest Chrome, Firefox and Safari.

        • Thanks for reply on behalf of me

  • Thanks Scott! I love your example, I hadn’t thought of using a radio selector with multiple labels but that seems like the smarter way to achieve this. Kudos!

    • 醉拳

      Hello Austin,

      if the user scrolls down on the page and then opens the menu, the browser will jump back to the top of the page. I guess there is not much I could do to circumvent this, since this is intrinsic to the current design of your menu, right?

  • Animating the entire page via a position property (eg. left) is very expensive because you are causing a lot of browser repaints, this will be noticeable on older devices. You should encourage your readers to use a property that can he hardware accelerated (like translate). I did a similar tutorial back in April ’13 but with the slide out menu only for small screens and using translateX for better performance: you might find it interesting. Also a bit of easing at the end of the transition would be nice.

  • mystrdat

    You cannot watch for keyboard events from CSS and adding it as a JavaScript feature would obviously defeat the whole purpose of it.

    • LouisLazaris

      It should work with tabindex, no need for JS. But the problem is that the checkbox isn’t visible. The author and I are going to look into it and maybe we’ll update it if we can get it to take focus. It just doesn’t seem to be working now, due to the hidden checkbox.

      • mystrdat

        True tabindex will work, my mistake.

  • LouisLazaris

    @matebrki:disqus / @mystrdat:disqus / @austinwulf:disqus

    It looks like I made a mistake, the checkbox is accessible with the keyboard, it’s just that in order to select/deselect a checkbox with the keyboard, you have to focus it and the press the space bar. I forgot about that.

    So I guess the problem now is how to indicate to the user that the checkbox has been focused, and that the space bar toggles it. I think it might be possible to add a tooltip or something on focus, using CSS, to indicate that, but that might be too much.

    You can also maybe assume that a prolific keyboard user would know how to do this, but he won’t necessarily know it’s a checkbox unless it’s a screen reader or something that announces it, which I’m not sure of.

    So I think it is a doable solution, but it would have to be tested for accessibility before committing to using it.

    • exodus

      If you add accesskey attribute to the checkbox element (accesskey=”m”) then u can trigger the menu with [alt+m] or [alt+shift+m] depending on browser.

      • LouisLazaris

        True, and that will improve things, because the menu becomes universal with the keyboard, but that still doesn’t solve the problem of the user not knowing that the menu is accessible via the keyboard. But yeah, that is a better solution for sure, thanks.

        • James Edwards

          No it isn’t :-) Accesskeys cause more problems than they solve.

          Look into aria attributes instead — something which semantically indicates the role and state of the widget.

          • LouisLazaris

            @disqus_9w7PMRX3XV:disqus Thanks, that’s just my ignorance of accesskeys there. When I first read the suggestion, I thought accesskey was obsolete, but when I looked it up, it was still in the HTML5 spec. Any idea why it’s still valid if it causes problems? Do you have a source link explaining the issues? Care to write something on it for us? :)

          • James Edwards

            There’s a good article on the subject here:

            The problem is that accesskeys conflict with existing shortcuts used by browsers and assistive devices. If your user-base is known and predictable (e.g. for a company’s intranet applications) then the issue might not apply, but for public websites there’s no getting around it.

            As to why they’re still in HTML5 … well, why are nested tables still allowed in HTML5? Why is the summary attribute obsolete? Why does the FONT element still exist? It’s because HTML5 doesn’t have the focus on accessibility that it should, it panders way too much to the convenience of creation and consumption software.

    • heydonworks

      Just add

      #nav-trigger:focus + label {
      outline: thin solid;
      outline-offset: 3px;

      • heydonworks

        Also, the label should have a text node or accessible name of some sort eg.

        <span class=”vis-hidden”>menu

  • LouisLazaris

    Ah, yes, this is a good point that I should have incorporated when I edited the article for Austin. I’ll see if I can do it and I’ll post an update here. Thanks, Dan!

    And for those interested, this is the proof of what Dan is talking about:

  • Ruff

    Hi, this is great, and I added this type of menu to an app I’m working on. But I got a problem when I browse this app on a mobile : the transform property apply a scalling on the site-wrapper. The wrapper don’t translate, it just scale down.

    It works fine on my desktop browser. Looks like a viewport issue, but I can’t find exactly what’s happening.

  • Ian


    Thanks for the article, really helpful but I’ve come unstuck when trying to move it on a step. I wanted to move the trigger inside the wrapper or at least into a containing DIV so it can be hidden when viewing on non-mobile. But as soon as I wrap it in a DIV even outside of the wrapper it stops it working. What am I not understanding?

  • This is a great technique! However, I notice that if I scroll the content, then open the menu in the above example, the content jumps back to the top… Scott O’Hara’s example does *not* do that, possible to implement the difference?

    • Floris

      you can change the positioning on the .nav-trigger from absolute to fixed, that should do the trick.

    • Gideon de Vries



    • Gideon de Vries

      Also use this:
      overflow-x: hidden;

  • CTN

    solid stuff. good work.

  • Wakkos

    This is an awesome technique. I`ve gone further and made a dropdown or multilevel menu:

  • 醉拳

    Hello Scott, I really like your innovative menu button solution. Do you have any browser support info for it, the GitHub link on your website (bottom end of the post) seems to be broken, I guess you have some more technical info on the GitHub description page.

    Thanks and kudos!

    • the github repo doesn’t actually have more information in it (and the link you referenced was actually for my website, but that has gone private, hence it breaking)

      The demo works in any browser that supports the :checked, + & ~ css selectors. The animation requires css transitions/transforms to be supported Basically IE9/10+ and modern versions of FF, chrome and safari.

  • Brian O’Neill

    If you have scrolled down the page and then select the hamburger icon, the page scrolls itself back to the top. The input is going to follow default form behavior, so there needs to be a way to prevent that. Could be done with JavaScript, I suppose, but that would defeat the purpose of a pure CSS solution. Does anyone have any ideas for a workaround?

  • angel

    Hi! this is pretty cool! I just want to know how do you use this for a parallax website? I tried using it and whenever i press the burger icon, the page goes back to the top

  • Udo ツ Springfeld™

    Awesome, thanks. If you use base64 encoding, it works in IEMobile too:

  • Gideon de Vries

    Hi you all, first of all, many thanks for your handy off canvas menu. I do recommend to use overflow-x: hidden; in .site-wrap. because otherwise the user can swipe horizontal. We wont want that.

  • Gideon de Vries

    Austin Wulf I was wondering if its possible to make a background bar (width 100%) in the nav trigger?

  • AndieR

    Really solid work, what would it take to make the menu stay put at the top of the page rather than on the side?

  • Hi, i was looking for this one, thank you Austin for the solution.

    I tried to make menu, slide from right and it worked. But, I want to know how do i make this work only for the mobile version and not the desktop version. Since now when i add ‘input’ and ‘label’ to the html, it shows up on the html page, but i need this to work only mobile or tab version.

    Could i please know how do i do it? Thank you.

  • Ian Baker

    I am only learning css but I have tried to make the menu slide out from the right but can’t seem to get it. The buttons yes but not the actual menu. Can anyone help?
    Also would love to know how to incorporate the radio buttons example as mentioned so there can be a X Close button in the menu…thanks for any advice

    • Sylvain Primeau

      I found a way to add a X Close button in the menu without using a radio button.

      1- Add a button in the navigation

      &_times;(remove the underscore)

      2- Add a small javascript in the head or body.

      function uncheck() {
      document.getElementById(“nav-trigger”).checked = false;

      3- In the CSS page, add a class .close and style it. Mine is styled like this.

      .close { cursor: pointer; color: #FFF; text-align: right; width: 200px; height: 50px; padding-right: 25px; font-size: 2.8em; display: block; top: 0; left: 0; border: none; outline: none; background: #333; position: relative; }

      .close:hover { background: #444; }

  • NaN

    Excelente!!! muchas gracias

  • Dallas Loghry

    Out of curiosity: Is it possible to replace the “ha,burger” with say, a right facing arrow that becomes a left facing arrow when the menu is out? I’m fairly new to this stuff.

  • Michael Barber

    It doesn’t work in later versions of IE. I see nothing in IE 11

  • Austin, thanks for the article! I love the pure CSS approach, and your example looks great. Unfortunately, when I try to incorporate it into my site design (including a full screen CSS “image slider”), I’m left pulling my hair out. The things that stick out to me are: I have to make the .site-wrap container “absolute” instead of “relative,” or the slider only appears as a narrow band at the top. Also, I can’t get the “right: 162;” declaration (the amount of shift in my case) to work at all for shifting the site-wrap…I have to use transform. The result works fine on Chrome desktop (resized for mobile), but on IOS or Safari of any kind, the drawer jerks open whereas the hamburger goes smooth.

    If there’s any chance you could tell me where I’m going wrong here, I’d be grateful…! I’ve put a copy of the html and CSS at:

    Thanks again for the article!

  • skozar

    Hey, it’s cool, but i updated it to work on focus action for text input. Now menu closes on any tap on screen. May be you would like this realization.

  • Dom

    I’m using this nav for an ongoing project but cannot override the default back-to-top. I’m using a typical hamburger but inside a sticky top bar so maybe that has something to do with it although I’ve played around removing this and using fixed position but with no success. Display none on the input doesn’t work, onclick=”function(); return false; doesn’t work either! Any ideas?

  • Rachel Montgomery

    Thank you this has been so helpful for my major work as I am new to css! I wondering however, which aspects would I need to change in order to make the menu on the right not the left…?

  • Spork Schivago

    Hi. Is there anyway to rig this up so there’s a top nav bar that’s always there unless the screen goes below something like 840px, and then the menu’s on the nav disappear and the bar just says Menu and all the way on the left appears the three bars that you have with the Smart phones and if someone touches those, then the page does what yours does? The top bar that says menu still stays there but under it, your stuff pops out? Also, if this is possible, is there anyway to do it so it could support older browsers as well as new? Currently, on my site, I check for older browsers and if they’re older (IE8 or older), I load an old version of jQuery and an older version of Normalize.css… How would I go about doing what I want done? Thanks.

  • Spork Schivago

    Never mind, I believe I got it.

  • Krzyś

    Nice. Just noticed that the hamburger button doesn’t show on IE11 @Win 8.1 though.

  • Very nice work..

  • Aga

    Hello. Thanks a lot for this! Great tutorial. I tweaked the css slightly so that the panel slides from top instead of the side. The problem I have is that when you scroll the menu hides under the content. How do I solve this so that the menu panel stayed on top unless the burger is clicked again? Thanks in advance

  • Aga

    Hello. Thanks a lot for this! Great tutorial. I tweaked the css slightly so that the panel slides from top instead of the side. The problem I have is that when you scroll the menu hides under the content. How do I solve this so that the menu panel stayed on top unless the burger is clicked again? Thanks in advance

  • Aga

    Hello. Thanks a lot for this! Great tutorial. I tweaked the css slightly so that the panel slides from top instead of the side. The problem I have is that when you scroll the menu hides under the content. How do I solve this so that the menu panel stayed on top unless the burger is clicked again? Thanks in advance

  • Kevin Levrone

    Thank you to the author for this article, great work !

    I am trying to emulate the Android drawer behavior in order to use such a page as an Android application. That behavior is very similar to the one presented here, but the “checkbox” trigger disappears/is hidden when the drawer is open, and in order to close the drawer you have to touch/click the content div. I wonder if it would be possible to implement such a thing in CSS only ? Maybe using the “:target” or even an overlay virtual “checkbox” label over the content div (which would probably negatively affect scrolling in the content div) ?

    Any help would be appreciated !

  • eglobian

    IE9 doesn’t work -_-

  • terminat

    Is this possible without label tag? (use :before or something similar instead?) The company i work for have a onlinebank which havn’t been updateted since 2002. Everything in the bank system is placed inside table/tbody. I do not have access to edit HTML/output.
    I can only do changes in CSS and want the bank to be mobile friendly for pads and mobilephones.

  • Xiaojie Duan

    Nice work! I use this code on my restaurant website, just for mobile version. But, today, I found that after the latest iOS update (9.0.2), it only works with safari. I tried chrome, dophie, opera mini, none of them working anymore. Please advise!

  • chris

    Great Slider. I installed it and got it running smoothly. I have noticed that the menu does not scroll until the page it is on scrolls to the bottom. This happens on Chrome desktop and Android phones. Is there a way to get it to scroll immediately?

  • J Munce

    Can this be edited so that when you open the hidden menu, it doesn’t return the device to the top of the page?

    I’d like to be halfway down the page, open the menu, close it, and be in the same place.

    Is this possible with this menu?

    • Fafafy

      Just found a way !
      Change the .nav-trigger position to fixed, and it will work well !

  • Jenn Hansen

    Hey there! I need some help. Everything is working fine for me except that the checkbox icons (both checked and unchecked) are not hiding underneath my hamburger icon :(

    I am using the latest version of iOS and have copied your code identically, but not luck. I’ve even tried alternate options… like 0 opacity, the display none option, and setting its position width and height to -999. Nothing works.

    Can you help?
    Thanks in advance and love your work!

  • Aaron Hodge Silver

    Clear, simple, and very nicely explained. Thank you!

  • Sebastian Bergholtz

    Very nice work. I want to add subcategoies to this but dont know how since im new to designing websites. Kan someone help me out. Thanks in advance!

  • Earl Jr. Hazel

    how would one put this ont the right of the website?

    • Shelton

      just use ‘right:’ property instead of ‘left:’

  • Bizzaro Galore

    I noticed that on my mobile device when I pull down on the main page, it slides down and you can see the menu underneath. How would I prevent that from happening? (other than that this is top notch I have found thus far)

  • Thomas Brown

    Is there anyway of adapting this to show a band of black from the menu so it appears as it its not completely off-screen? tried a few things without success

    • Shelton

      Hi Thomas, you just need a ‘left: 5px’ on the wrap class so that the main content is 5 pixels from the left, thus showing the edge of the menu (because it’s just sitting behind everything)

      hope it works for you

  • Sharif Hamdy

    This is exactly what I was looking for, Ill link you soon as i get it in my code…thank you!

  • Hello, that what i looking for. thank you

  • Дмитрий Силаев

    OK! Where is project?

Get the latest in Front-end, once a week, for free.