Mobile-friendly toggling menu

My selfie

James Turner,

Tags: CSS HTML JavaScript

A question I've sometimes puzzled over is how to design a site's main navigation, with say six menu items, so that it looks and behaves in a pleasing way at small device widths. The solution that Responsive Web Design uses is elegant without being too flashy, and uses all three of the web design trio: HTML, CSS and JavaScript. It's only in the last couple of months that I've been delving into JavaScript, so I'm particularly proud of myself for figuring this it out!

Skip to navigation

All menus, great and small

At full mast (desktop screen width), the main navigation appears in the header as a row of links that balance beautifully with the site logo.

At desktop size, Responsive Web Design's navigation menu spreads out pleasingly across the width of the screen.
Figure 1: Responsive Web Design's navigation menu at full mast.

At smaller screen widths, the menu seems to disappear, and it's replaced with a black dot in the corner (Figure 2, left). Click or tapping this dot reveals the menu (Figure 2, middle), and a dash of animation to shows the user what's happening (Figure 2, right).

Side-by-side, we have the three states of the nav menu. The closed menu state appears as a large black dot in the top corner that says 'menu'. When clicked or tapped, the menu extends to a single-column menu with five items. The menu animates, taking less than a second to extend down and pushing the content below it.
Figure 2: The closed (A), open (B) and animated (C) states of Responsive Web Design's mobile-sized navigation menu.
Skip to navigation

90% styling, 10% script

What's the secret? It's so simple, it's wonderful! When the toggle is clicked or tapped, a very short script adds or removes a class called is-open to the <nav> element. Done. The rest is CSS styling, which controls what the nav element and its children look like with and without the is-open class. This includes a 0.3-second transition on the nav element's max-height attribute, which gives us our helpful animation.

Below, I've given my spin on the HTML, CSS and JS required to pull this off.

Skip to navigation

HTML markup

The markup for the nav menu is fairly standard:

<nav>
  <a href="javascript:void(0)" id="menu-toggle">MENU</a>
  <ul>
    <li><a href="#">Home</a></li>
    <li><a href="#">Courses</a></li>
    <li><a href="#">Publications</a></li>
    <li><a href="#">Blog</a></li>
    <li><a href="#">About</a></li>
    <li><a href="#">Contact</a></li>
  </ul>
</nav>

In my project, I tried using href="#" for the target of the menu button. The problem was that because the menu button wasn't right at the top of the page as it is in RWD, the screen would lurch jarringly back to the top. I found that href="javascript:void(0)" prevented this from happening.

Skip to navigation

CSS styles

The CSS styles are spread across several parts. I've only included the most essential styles here; each project will require its own colours, typography choices, and so on. My key breakpoint is 35em. Below this, the menu is directed by the toggle. Above this, the menu is constantly in view and the toggle button is hidden.

Firstly, this is the styling for the toggle button at a screen width lower than 35em:

@media (max-width: 35em){
  #menu-button {
    display: block;    /* display as block */
    overflow: hidden;  /* precautionary; stops text from overlapping edges */
    margin: 0 auto;    /* centres the button */
    line-height: 3.9rem;  /* by trial and error, centres the word 'menu' vertically  */
    border-radius: 50%;  /* creates a round button; works in conjunction with width and height */
    background-color: #000;
    text-transform: uppercase;
    color: #fff;
    width: 4rem;    /* sizes button */
    height: 4rem;    /* sizes button */
    text-align: center;
    text-decoration: none;
    font-size: 0.75rem;
  }
}

Above 35em, the toggle button disappears completely.

@media (min-width: 35em){
  #menu-button {
    display: none;
  }
}

At small screen sizes, the nav menu is a single column. Without the is-open class, the max-height is 0. It is this attribute that opens and closes the menu.

@media (max-width: 35em){
  nav ul {
    max-height: 0;      /* KEY: this is what 'closes' the menu */
    overflow: hidden;    /* Stop menu items from appearing below container */
    transition: max-height .3s;  /* Add gentle animation to menu opening and closing */
  }
  .is-open ul {
    max-height: 20em;    /* KEY: this is what 'opens' the menu
               Note, 20em is an arbitrary max-height; it just
               needs to be longer than all the menu items. */
  }
}

Above 35em, the nav menu appears however you want.

@media (min-width: 35rem) {
  /* Styles for menu over 35em */
}
Skip to navigation

JavaScript code

In pseudocode, here's what we do:

// When the menu toggle is clicked or tapped,
// toggle the boolean open/closed state
// If state = open,
// add "is-open" to the nav element's list of classes
// If state = closed,
// remove "is-open" from the nav element's list of classes

Here it is in JS:

var menuOpen = false;
var menu = document.querySelector("nav");
var classToAdd = "is-open";

function spaceIfRequired() {
  // if the menu has just been opened
  if (menuOpen){
    // if the class length is 0, there must be no other classes
    // so there's no need to add a space
    // otherwise add a space
    return (menu.className.length == 0)?"":" ";
  } else { // if the menu has just been closed
    // if the total class length is equal to 'is-open' length,
    // there must only be one class, so no need to add space
    // otherwise add a space
    return (menu.className.length == classToAdd.length)?"":" ";
  } // end if menuOpen
} // end function

// MAIN
document.getElementById('menu-toggle').onclick = function(){
  // toggle menu
  menuOpen = !menuOpen;
  // if the menu has just been opened
  if (menuOpen) {
    // add "is-open" class to the nav element
    menu.className += spaceIfRequired() + classToAdd;
  } else { // if menu has just been closed
    // remove "is-open" class from the nav element
    menu.className = menu.className.replace(spaceIfRequired() +
      classToAdd, "");
  }
} // End of .onclick

The spaceIfRequired() function adds a space if the nav element has other classes attached to it. When minified, this script needs just 263 bytes, so it is definitely worth the extra weight that it adds.

Skip to navigation

Accessibility

Testing this toggle menu on my devices with all the main browsers, there don't seem to be any problems. Let's try and break it!

Keyboard: I found that I can navigate to the toggle button using the tab key, and in fact, the menu doesn't even need to be open to tab through the menu items. One small problem is that the toggle button doesn't change at all when it has the focus. Perhaps it might be better to change the colour in CSS with #menu-toggle:focus { /* styles */ }.

Screen reader: Using WebbIE 4, I tried navigating a site I made with this menu structure, and it performs perfectly.

Old browsers: The nav element isn't supported by IE8 or lower, so if it's important to design for this browser, you'd be better off using a div element for the navigation. Apart from that change, the toggle menu should theoretically work.

Skip to navigation

More applications

If we squint just so, we'll almost certainly be able to find other applications for this technique. It would be useful in situations where the user might want to toggle content. For example, imagine a very long, text-heavy document that, for some reason, needs to be contained in a single page. Headings could function as toggle buttons. Clicking or tapping on a heading hides or reveals the content below.

Another potential application I can think of is to include controls that allow the user to change the font, font size, capitalisation, and background colour. These are parameters that can influence the readability of a website, in particular for users with dyslexia and/or visual impairments. Skip to navigation

Conclusion

How nifty is that? So, using a tiny bit of JavaScript to add a class to an object, we can create an elegant, mobile-friendly menu that switches on and off. The beauty of it is that it seems to have no impact on a site's accessibility.

Further reading

This great pen by Geoffrey Crofte demonstrates a very similar technique. The basic functionality is the same. The main difference is that instead of displacing the content below the menu when it opens, the menu is taken out of the flow of the document using relative positioning, and its appearance is controlled with visibility, not max-height.

Update 24/11/2015: Purely by coincidence (I promise), I picked up Ethan Marcotte's Responsive Design: Patterns and Principles not long after I wrote this. In Chapter 2, he covers toggling menus with much more insight than me. In particular, he shows how to ensure that the menu stays visible when the JavaScript fails. Genius!

Skip to navigation