Grow your CSS skills. Land your dream job.

Responsive Menu Concepts

Published by Guest Author

The following is a guest post by Tim Pietrusky. I know Tim from his prolific work on CodePen and from being a helpful community member there. He wrote to me with this guest post about responsive menus which I'm more than happy to share with you below. Not only is it a timely concept, but one of the concepts improves upon a clever CSS trick we've covered here in the past.

When it comes to responsive design we are faced with various techniques on how to best handle altering our navigation menus for small screens. The resources seem endless. That’s why I’m going to show you four main concepts and discuss the advantages and disadvantages of all of them.

Three of them are made with pure CSS and one uses a single line of JavaScript.

Before We Start

In the code presented in this article, I don’t use any vendor-prefixes to keep the CSS easier to see and understand. The more complex CSS examples use SCSS. Each example is hosted on CodePen where you can see the compiled CSS if you wish.

All menu concepts in this article are based on this simple HTML structure which I call basic menu. The role attribute is used to specify the particular concept (full-horizontal, select, custom-dropdown and off-canvas).

<nav role="">
  <ul>
    <li><a href="#">Stream</a></li>
    <li><a href="#">Lab</a></li>
    <li><a href="#">Projects</a></li>
    <li><a href="#">About</a></li>
    <li><a href="#">Contact</a></li>
  </ul>
</nav>​

To address small screens I use the same media query on all concepts.

@media screen and (max-width: 44em) {

}

1. Full Horizontal

This is the most simple approach because you just need to make the list elements full width on small screens.

<nav role="full-horizontal">
  <ul>
    <li><a href="#">Stream</a></li>
    <li><a href="#">Lab</a></li>
    <li><a href="#">Projects</a></li>
    <li><a href="#">About</a></li>
    <li><a href="#">Contact</a></li>
  </ul>
</nav>​
@media screen and (max-width: 44em) {
  nav[role="full-horizontal"] {
    ul > li {
      width: 100%;
    }
  }
}

This is what it looks like on a small screen with a custom style.

full-horz

Advantages

  • No JavaScript
  • No extra HTML
  • Simple CSS

Disadvantages

  • Reserves too much screen-space

Demo

On CodePen:

2. Select

This concept hides the basic menu on small screens and shows a select menu instead.

To achieve this we need to extend our basic markup and add a select. To get the select working we also add some JavaScript which alters window.location.href when the onchange event on the select occurs.

<nav role="select">
  <!-- basic menu goes here -->
  
  <select onchange="if (this.value) window.location.href = this.value;">
    <option value="#">Stream</option>
    <option value="#">Lab</option>
    <option value="#">Projects</option>
    <option value="#">About</option>
    <option value="#">Contact</option>
  </select>
</nav>​

We hide the select on big screens.

nav[role="select"] {
  > select {
    display:none;  
  }
}

On small screens, we hide the basic menu and show the select. To help the user recognize that this is a menu we’re also adding a pseudo-element with the text “Menu”.

@media screen and (max-width: 44em) {
  nav[role="select"] {
    ul {
      display: none;
    }

    select {
      display: block;
      width: 100%;
    }

    &:after {
      position: absolute;
      content: "Menu";
      right: 0;
      bottom: -1em;
    }
  }
}

This is what it looks like on a small screen with a custom style.

num-2

Advantages

  • Doesn’t need much space
  • Uses native controls

Disadvantages

  • Needs JavaScript
  • Duplicate content
  • Select is not styleable in every browser

Demo

On CodePen:

3. Custom Dropdown

This concept hides the basic menu on small screens and shows an input & label (to use the Checkbox Hack) instead. When the user clicks on the label, the basic menu is shown underneath.

<nav role="custom-dropdown">
    <!-- Advanced Checkbox Hack (see description below) -->
    
    <!-- basic menu goes here -->
</nav>​

Problem with the Checkbox Hack

There are two problems with the default Checkbox Hack:

  1. Doesn’t work on mobile Safari (iOS < 6.0). It’s not possible to click the label on iOS < 6.0 to toggle the input due to a bug. The only solution is to add an empty onclick to the label.
  2. Doesn’t work on the default Android browser (Android <= 4.1.2). Once upon a time there was a WebKit Adjacent/General Sibling & Pseudo Class Bug which prevented the use of pseudo-classes combined with adjacent (+) or general (~) sibling combinators.
h1 ~ p { color: black; }
h1:hover ~ p { color: red; }

This has no effect because the checkbox hack uses the pseudo-class :checked combined with the general sibling. And since this was fixed in WebKit 535.1 (Chrome 13) and the actual WebKit on Android 4.1.2 is 534.30, the normal checkbox hack doesn’t work on any Android device to date.

The best solution is to add a WebKit-only fake animation on the body element.

All stuff combined creates the Advanced Checkbox Hack:

<!-- Fix for iOS -->
<input type="checkbox" id="menu">
<label for="menu" onclick></label>
/* Fix for Android */
body { 
  -webkit-animation: bugfix infinite 1s; 
}
@-webkit-keyframes bugfix { 
  from { padding: 0; } 
  to { padding: 0; } 
}

/* default checkbox */
input[type=checkbox] {
  position: absolute;
  top: -9999px;
  left: -9999px;
}

label { 
  cursor: pointer;
  user-select: none;
}

Reference: Advanced Checkbox Hack

For large screens, we hide the label:

nav[role="custom-dropdown"] {
  label {
    display: none;
  }
}

For small screens, we hide the basic menu and show the label. To help the user recognize that this is a menu we’re also adding a pseudo-element with the text “≡” (converted to “\2261” to use it as content on the pseudo-element) to the label. When the user clicks on the input, the basic menu gets shown and the list elements are expanded to full width.

@media screen and (max-width: 44em) {
  nav[role="custom-dropdown"] {
    ul {
      display: none;
      height: 100%;
    }

    label {
      position: relative;
      display: block;
      width: 100%;
    }

    label:after {
        position: absolute;
        content: "\2261";
    }
    
    input:checked ~ ul {
      display: block;
    
      > li {
        width: 100%;
      }        
    }
  }
}

This is what the menu looks like on a small screen with a custom style.

closed
Closed
open
Open

Advantages

  • Doesn’t need much space when closed
  • Custom styling
  • No JavaScript

Disadvantages

  • Bad semantics (input / label)
  • Extra HTML

Demo

On CodePen:

4. Off Canvas

This concept hides the basic menu on small screens and shows a HTML input & label (to use the Advanced Checkbox Hack, see 3. Custom Dropdown for more infos) instead. When the user clicks on the label, the basic menu flies in from the left and the content moves to the right – the screen gets divided: menu ~80 % and content ~20 % (depends on resolution and css units).

<input type="checkbox" id="menu">
<label for="menu" onclick></label>

<!-- basic menu goes here -->

<div class="content">
  <!-- content goes here -->
</div>

On large screens, we hide the label.

label {
  position: absolute;
  left: 0;
  display: none;
}

On small screens, we hide the basic menu outside the viewport and show the label / input. To hide the menu we specify a width ($menu_width) and add a negative position to it. To help the user recognize that this is a menu we’re also adding a pseudo-element with the text “≡” (converted to “\2261” to use it as content on the pseudo-element) to the label.
When the user clicks on the input, the basic menu flies in from the left and the content moves to the right.

@media screen and (max-width: 44em) {
  $menu_width: 20em;

  body {
    overflow-x: hidden;
  }
    
  nav[role="off-canvas"] {
    position: absolute;
    left: -$menu_width;
    width: $menu_width;
    
    ul > li {
      width: 100%;
    }
  }

  label {
    display: block;
  }

  label:after {
    position: absolute;
    content: "\2261";
  }

  input:checked ~ nav[role="off-canvas"] {
    left: 0;
  }

  input:checked ~ .content {
    margin-left: $menu_width + .5em;
    margin-right: -($menu_width + .5em);
  }
}​

This is what the menu looks like on a small screen with a custom style.

offcanvas-closed
Closed
offcanvas-open
Open

Advantages

  • Doesn’t need much space when closed
  • Custom styling
  • No JavaScript
  • Convention from Facebook / Google+ app

Disadvantages

  • Bad semantics (input / label)
  • Extra HTML
  • Absolute position to the body = Feels like fixed position

Demo

On CodePen:

Does it Work on IE?

All of the techniques used above have one goal: Create responsive menus for modern browsers! And because there is no IE 8 or lower on any mobile device we don’t need to worry about it.

Comments

  1. Permalink to comment#

    thank you for this article, one remark though :
    Instead of using the checkbox hack, it is possible to use the :target pseudo-class
    see Raphael Goetter’s experiments here http://thinkmobilefirst.net/nav/
    I’m not aware of specific device limitations, I would love to have feedback on this, I just deployed it on
    http://www.rescue2014.fr (resize your browser, obviously)

    • :target is ideal for semantics, but a bummer in that it adds history items (affects back button).

      :checked is less good semantically but functionally is better.

      Tough situation.

    • cnwtx
      Permalink to comment#

      This would be a case where I would tend to ditch the semantics. We’re probably the only ones that are going to see and actually care about semantics, and the non-semantic version works better. So, I’d tend to just use what works. The average user of the site probably won’t see the code, and, if they do, they probably won’t care about semantics.

    • Ando
      Permalink to comment#

      @cnwtx
      True, but more accessibility-oriented user agents (screen readers, etc.) rely on semantics to find the elements of the site that the user wants displayed / read.

    • cnwtx
      Permalink to comment#

      @Ando, True, but I would tend to think that screen readers, etc will tend to see the <nav> instead of :checked or :target. You’ve certinaly brought up a good point, though.

    • Ando
      Permalink to comment#

      @cwntx
      Yeah, to be honest, I’m not entirely sure how screen readers would function with regards to inputs and lables within a nav tag. It could go either way as far as I know, haha.

  2. Awesome article Tim! Very clear and providing many ways to do one thing, I like it.

    By the way, this checkbox hack addition is completely sick, I wonder how you could even think of something like that!

  3. Permalink to comment#

    I think that use of attribute role isn’t a good idea, you can use data-* attributes instead of it.

    http://www.w3.org/TR/xhtml-role/

    http://ejohn.org/blog/html-5-data-attributes/

    • I will think about the use of data- instead of role-attributes! Thank you.

    • Permalink to comment#

      I would strongly agree; it’s the very first thing that caught my eye.

      role should define (in a machine-readable way) the purpose of an element, but here, you’re using it to define how the element is presented.

      As @Israel suggests, using data-* attributes (or even a class) would be more appropriate.

  4. Great roundup! I’ll probably come here 1000 times in the future.

    It seems these stylings groups could be based off a simple HTML class rather than the role attribute. The role attribute is typically, although not exclusively, reserved for ARIA roles, which are confusing already but are a set of pre-defined roles that have meaning to other machines.

    • I didn’t know that the role attribute is reserved for ARIA roles. Thanks for the info!

    • Permalink to comment#

      Not technically reserved per say but it serves its purpose for ARIA / screen readers etc. What you did technically wasn’t wrong as you just selected by attribute, but I’m sure others will advise you to the “data-” HTML5 attribute approach as its designed specifically to handle things the way you’re looking to. What you need to be careful with when it comes to HTML5 custom data attributes is that plugins/libraries code may leverage the same naming conventions you will without you realizing it. A Standard enough naming convetion like img src=”” data-index=”0″ alt=”Bobs Hair” could possibly be used in conflict throughout your application, so just be mindful of your naming conventions.

  5. Martin Blackburn
    Permalink to comment#

    Some great solutions there, given me lots of ideas and things I want to try.

    Here is one I have been working on, its doesn’t need JavaScript, but its a bit nicer with it. Its all on github so if anyone wants to use/tinker with it feel free :)
    martinblackburn.github.com/responsive-nav/

  6. When I do the checkbox hack, to get over the iOS bug I just style the <input> instead with (-prefix-)appearance: none; at the beginning to override default styling.

    • The iOS bug is not about styling. It prevents you from clicking on the label to toggle the input (checked / not checked). And if you want to fix the bug, you could add an empty onclick onto the label or place the input (with full height/width) in front of the label.

    • The point that I was making is that you don’t even need a label if you style the input. I know it’s not about styling. You can still have the icon with a ::before and maybe even have a hidden label for SEO, but it removes the need to have that empty onclick.

    • Now I get what you mean. That sounds like a really cool improvement to get rid of the label. But unfortunately it’s not supported in the latest Opera or Internet Explorer 10.

    • That’s true, although I think that by adding a border, the default appearance is overridden anyway (I know it is with text inputs).

  7. @Martin: I’ve done a very similar thing, and is my favourite approach. But the last on the post (Off canvas) is pretty good, guess I’ll do a combination, generally I steer clear of form elements for navigation.

  8. Permalink to comment#

    Another possibility, if you don’t mind a little JavaScript, is Brad Frost’s toggle method in lieu of the checkbox hack. Requires javascript but is well supported. I made a demo on CodePen.

  9. GAtkins
    Permalink to comment#

    Very nice guys, thank you.

    Glenn

  10. Permalink to comment#

    Awesome, I’m always learning, THX a lot

  11. Katsampu
    Permalink to comment#

    Nice work, thanks for sharing.

    Anyway, i don’t think labels and inputs or options are semantically right for navigation.

    What’s wrong with Javascript, and adding a class in nav? Why is it disadvantage? Simple markup, clean css, simple js.

    • Permalink to comment#

      I’m of the same opinion. I think it would be better using classes with simple Javascript in place of form elements.

    • The only disadvantage of using JavaScript is that it won’t work when JavaScript is disabled.

      As described in the article adding label/input is not semantically right. It’s just one way to handle this kind of menu without the use of JavaScript. But you can extend these concepts as you like! Just keep in mind to share it with the community.

    • Nate
      Permalink to comment#

      I agree that javascript is a good solution. It gets around the messiness of form elements and/or duplicate content. If you’re designing mobile first—starting with reasonable markup and functionality for those without javascript—you’re good to go.

      My solution for really big or complex menus is to put the menu in its own page. Users without javascript who click on the ‘Menu’ button go to the separate menu page. For those with JS support, I load the menu in via ajax, and then use javascript to hide and show the menu appropriately.

  12. Mitchell
    Permalink to comment#

    I think the best right now is the first option, full horizontal.

    Yes, it can take up a lot of screen space if you have many top-level nav items, but its the only one that has no other downsides.

    Perhaps in the future we’ll have better semantically-correct options, until then I feel this method is the safest.

  13. Nice write up Tim!

    As Dave wrote above, this article is a good reminder to keep in mind when creating a new responsive menu.

  14. Is it possible to add sub menus?

  15. Jonathan Cardoso [JCM]
    Permalink to comment#

    Chris, It would be interesting to be able to resize the codepen iframes, so we can see the media queries in action without needing to open in another tab, and them, resize the browser.

  16. Joanna
    Permalink to comment#

    It is interesting and useful.

  17. Permalink to comment#

    Awesome Menu’s. :)

  18. iamthestig
    Permalink to comment#

    Is there a way for that scss to be converted in just normal css.

  19. Permalink to comment#

    Hi – very well written post!

    So right from the top, this post seems to do for desktop first, with mobile mods to make the small screens behave nice.

    Is this a better overall result than a mobile first approach, with all mods making the desktop code perform better? Cheers

  20. Patrick
    Permalink to comment#

    My choice would be to use the “select” menu with a fallback to “full horizontal” and use javascript to show the “select” menu and hide the “full horizontal”.

  21. ChillyPenguin
    Permalink to comment#

    The examples don’t work on my iPhone; I just get the “full size” page.

    Is this a limitation of codepen? It doesn’t let you set the viewport meta tag?

  22. For me the best solution is still a combination between 1 and 3, or you could even do 1 and 4. Meaning that you have a menu that is visible at the top (or bottom) of the page by default and turn that into one of the other solutions when the page loads with JS. Then you can use JS events for triggers and aren’t reliant on the checkbox hack, which seems like just that… a hack. Thanks for the post!

  23. Permalink to comment#

    Great write-up Tim! And thanks again for your help with the navigation menu plugins I’ve been working on.

  24. Permalink to comment#

    Perfect categorization and comparison , just as always!
    Thanks!

  25. Permalink to comment#

    As @Vivek Nath.R mentioned above, none of the examples address sub-navigation. While I would always love to build websites without any sub-nav, it’s just not possible with certain clients (or websites). A big challenge with responsive navigation seems to be how to handle large menus. Brad Frost has some great examples here. But great post, it’s nice to see CodePen being used so well.

  26. JGarrido
    Permalink to comment#

    It probably would have been good to include the word ‘mobile’ in the title of the post.

  27. Permalink to comment#

    Very nice explained and detailed article. I was looking for this, and definitely I will use the Custom Dropdown approach
    PD. Where is the share link?

  28. How would you go about making a menu which uses hover for dropdown on PCs, and clicks on Mobile?

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".