Responsive Menu Concepts

Avatar of Tim Pietrusky
Tim Pietrusky on (Updated on )

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

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.