A Responsive Design Approach for Navigation, Part 1

Posted by Maggie on 02/27/2012

Topics:

As we create responsive websites, we must consider a number of factors to make sure both the design and code are as bullet-proof as possible: the design must scale across a wide range of screen sizes from mobile to tablet to desktop; and the code must start with a mobile-first approach, work well for screen readers or with JavaScript disabled, and be robust enough to adapt to differences in text size and rendering across devices or user settings.

Adapting site navigation to be understandable, usable and attractive across a wide range of devices is particularly challenging. On the Boston Globe site, we gained some key insights about how to build robust mobile-first responsive navigation systems. This is the first in a series of articles in which we'll explore navigation design techniques from very simple nav bar design, to complex multi-level hierarchies, search, and fuller featured navigation systems.

Truly responsive navigation

Using progressive enhancement, CSS media queries, and a little JavaScript, we built a navigation list that adjusts to fit the size of the screen and adapts to differences in text sizing. View the demo

We're in the process of rethinking our site design, so we used our own navigation list as a test case. It consists of 5 options — What We Do, What We've Done, What We're Thinking, Who We Are, and Contact Us. These options can take up a lot of space on a tiny mobile screen, so our goal was simple: for smaller screens we'll make the navigation as compact as possible to save room for the main content, and on larger screens with ample space we'll display all options in a horizontal bar for quicker access.

We marked up a standard unordered list of links, usable on any device that renders HTML, layered on a few mobile-friendly styles to make the hit area bigger for finger taps, and then added a little JavaScript to transform the list into a custom dropdown menu. Users that don't have JavaScript enabled will just see the full list — no harm, no foul.

For larger screens, we used CSS3 media queries to display the options in a horizontal bar. We wanted to ensure that the screen has enough room to display the entire bar in a single line (we don't want the nav or its options to wrap because, in this case, wrapping would make the page look broken or mess with the navigation hierarchy), so we estimated the minimum width value: width of the horizontal navigation bar + the width of any neighboring elements + a little wiggle room. We tested this breakpoint across a wide range of browsers, and increased the value when we saw options wrap.

We could've stopped there, but we can't forget that screen size is just one of many factors that effect how a layout is displayed. Take text rendering, for example: the type, size, and shape of fonts can vary among operating systems/browsers, and users have the option to adjust sizing with browser controls. (And, as Stephanie Rieger notes, the list of user-controlled variables is growing.) So, while it makes sense to use media queries as a baseline for altering the layout, we made our navigation bar feel smarter and more responsive with a little JavaScript to detect whether the horizontal nav bar fits in the available space, regardless of screen size; when the fit test fails, the navigation remains a custom dropdown menu.

The final result is a navigation list that's optimized for use on a wide range of screen widths and with variable text sizes. We'll walk through this example in detail below.

Markup

The menu is a list of links, so that's what we'll use: a standard unordered list with anchor tags. We'll group the list with a heading to identify the navigation element, "Sections", and give the outer container a descriptive class for scoping styles, nav-primary.

<div class="nav-primary">
   <h3>Sections:</h3>
	<ul>
		<li><a href="http://www.filamentgroup.com/services">What We Do</a></li>
		<li><a href="http://www.filamentgroup.com/portfolio">What We've Done</a></li>
		<li><a href="http://www.filamentgroup.com/lab">What We're Thinking</a></li>
		<li><a href="http://www.filamentgroup.com/about">Who We Are</a></li>		
		<li><a href="http://www.filamentgroup.com/contact">Contact Us</a></li>
	</ul>
</div>

We'll assign the class nav-current to the active navigation option; this will come in handy later when we create the custom dropdown menu.

<li class="nav-current"><a href="http://www.filamentgroup.com/services">What We Do</a></li>

To round out our example, we'll add the Filament Group logo and a small block of content. For screen readers we'll provide a "skip navigation" link so that they don't have to navigate through the list on every page.

<a href="http://www.filamentgroup.com/" id="logo"><img src="fg-logo.gif" alt="Filament Group, Inc." /></a>

<a href="#main" class="skip">Skip navigation</a>

<div class="nav-primary">
   <h3>Sections:</h3>
	<ul>
		<li class="nav-current"><a href="http://www.filamentgroup.com/services">What We Do</a></li>
		<li><a href="http://www.filamentgroup.com/portfolio">What We've Done</a></li>
		<li><a href="http://www.filamentgroup.com/lab">What We're Thinking</a></li>
		<li><a href="http://www.filamentgroup.com/about">Who We Are</a></li>		
		<li><a href="http://www.filamentgroup.com/contact">Contact Us</a></li>
	</ul>
</div>

<div class="content">
   <p>We offer services from strategy to UI and application design, to accessible front-end code development, and happily work with clients to find the right mix that supports their internal capabilities.</p>
</div>

Before we wrap up the markup, we want to make sure that our page content is sized to fit the screen, so we'll add a viewport meta tag (how the viewport meta tag works):

<meta name="viewport" content="width=device-width, initial-scale=1">

We now have a legible, usable page to which we can add style and behavior enhancements.

Basic markup rendered on a mid-sized screen

Styles for the small screen

If you're not familiar with using CSS3 media queries to render pages responsively, we highly recommend Ethan Marcotte's definitive article, Responsive Web Design, and book by the same title.

We'll start with a few basic styles that make the links easier to read and tap on small screens (a block display property and padding, and a font-size that's large enough for finger-based gestures), and a few additional styles to make the list easier to scan (borders between options, and bold text for the active option). We'll also hide the "Sections" heading (h3), but in a way that keeps it accessible to screen readers.

.nav-primary h3 {
   position: absolute;
   left: -999em;
}
.nav-primary ul {
   border: 1px solid #e6e6e6;
}
.nav-primary li {
   font-size: 1.8em;
   border-bottom: 1px solid #eee;
}
.nav-primary li:last-child {
   border-bottom: 0;
}
.nav-primary a {
   display: block;
   padding: .5em .8em;
   text-decoration: none;
   color: #333;
}
.nav-primary a:hover {
   background-color: #f8f8f8;
}
.nav-primary .nav-current {
   font-weight: bold;
}

We'll add a few more rules to style the overall page, logo and content blocks, and another rule to make the navigation play nicely with them (clear the logo, and add space before the content). We want the "skip" link to be available to screen readers but hidden from everyone else, so we'll position it off the page.

body {
    font: 62.5%/1.4 helvetica, arial, sans-serif; 
    padding: 2em 1em;
}
#logo {
   float: left;
   margin: 0 0 1em; 
}
.content {
   clear: both;
}
p {
   font-size: 1.8em;
   line-height: 1.4;
   margin: 0 .3em 1em;
}
a.skip {
   position: absolute;
   left: -999em;
}

...

.nav-primary {
   clear: left;
   margin: 0 0 2em;
}

Basic styles rendered on a small screen

...and for larger screens, like tablets and desktops

Our page looks nice at smaller screen widths, but on a larger screen, each option stretches to fill the space and the navigation dominates the page.

Basic styles rendered on a larger screen; the navigation takes up too much space

To fix this, we'll add a couple of rules, scoped within a media query, to display the navigation as a horizontal bar in screens that are 640px wide and larger (640 = full width of the navigation + body padding).

We'll float each navigation option to the left, remove the border between options, and assign a slightly smaller font size. We'll also float the navigation block itself and the list container so that they fully contain their floated child elements.

@media screen and (min-width: 640px) {
   .nav-primary,
   .nav-primary ul {
      float: left;
   }
   .nav-primary ul {
      float: left;
   }
   .nav-primary li {
      float: left;
      font-size: 1.5em;
      border-bottom: 0;
   }   
}

Much better.

Navigation rendered as a horizontal bar on screens 640px or wider

When the screen is wide enough to fit the logo and nav side-by-side — 910px, specifically — we'll float the navigation block to the right. We arrived at that number by adding the width of the logo (250px) to the width of the navigation list displayed horizontally (640px), plus a little extra margin to account for slight browser rendering differences (20px).

@media screen and (min-width: 910px) {
   .nav-primary {
      float: right;
      clear: none;
   }   
}

Our navigation now assumes one of two forms in response to the screen's size: a list on smaller screens, or a horizontal bar on larger screens.

Examples showing a list on smaller screens, or a horizontal bar on larger screens

As it stands, our navigation looks pretty good on a range of screen sizes — but there is definitely room for improvement. On small screens, our navigation list still takes up a good chunk of vertical space, and on larger screens, our horizontal navigation bar wraps when we increase our browser's text size. Bump up the text a couple of times on a tablet-sized screen and the options wrap to a second line:

Navigation options wrap to a second line when text size is increased

And at larger sizes, the entire navigation bar wraps under the logo, creating odd gaps of white space:

Navigation bar wraps under the logo when text size is increased

So we have two issues left to address:

  • transform the navigation into a compact, custom dropdown menu when screen space is limited, and
  • create a test to determine if navigation options will fit on a single line, or if the entire bar will fit next to the logo on larger screens; if not, show the compact menu.

We'll start with menu styles, and then fill in the test logic.

Styles for a compact menu

We'll write styles to hide all options by default except for the active option, nav-current, and display the "Sections" heading as a clickable dropdown button positioned to the right. We'll add another class, expanded, that we'll toggle with JavaScript to show/hide the full list of options.

Note that these styles are all scoped to the class, nav-menu. Later, our test logic will toggle this class on the body tag.

.nav-menu .nav-primary {
   margin: 0 0 1.5em;
   position: relative;
   overflow: hidden;
}
.nav-menu .nav-primary a {
   padding-right: 3em;
}
.nav-menu .nav-primary h3 {
   position: absolute;
   top: 0;
   left: auto;
   right: 0;
   display: block;
   width: 4em;
   height: 4.5em; 
   background: #ccc url(img/icons.png) no-repeat -205px 45%;
   text-indent: -999em;
   cursor: pointer;
}
.nav-menu .nav-primary.expanded h3 {
   background-position: -169px 45%;
}
.nav-menu .nav-primary li {
   clear: left;
   display: none;
}    
.nav-menu .nav-primary.expanded li,
.nav-menu .nav-primary li.nav-current {
   display: list-item;
} 
.nav-menu .nav-primary li.nav-current {
   border-bottom-width: 0;
}
.nav-menu .nav-primary.expanded li.nav-current {
   border-bottom-width: 1px;
}

Now, when JavaScript is enabled, the menu will take a more compact form:

Navigation rendered as a compact menu

Next, we'll write a bit of JavaScript to ensure that our navigation bar fits the available space.

Does the navigation fit?

We'll bind a custom event, "testfit", to the navigation container. We'll trigger this event when the page loads, and when the screen changes size or orientation. (A nice side effect: desktop browsers that support text zooming, like the latest versions of Chrome, Firefox, and Opera, trigger the resize event — and this test — when the user increases or decreases text sizing with browser controls or key commands.)

$('.nav-primary')
   // test the menu to see if all items fit horizontally
   .bind('testfit', function(){
      // ...logic goes here...
   }) 
   
   // ...and update the nav on window events
   $(window).bind('load resize orientationchange', function(){$('.nav-primary').trigger('testfit');});

When the test passes, we'll add the nav-menu class to the body. One of the following conditions must be met for "testfit" to pass: when the entire nav wraps to a second line under the logo, or when any of the navigation options wrap to a second line. In both cases, we test for wrapping by comparing top offset values, which can be effected by changes in screen or text size. When the navigation's top value is greater than the logo's, the navigation has wrapped to the next line; when the last navigation option's top value is greater than the first, it's wrapped.

We'll also remove the nav-menu class from the body to reset the styles before the test is run.

$('.nav-primary')
   // test the menu to see if all items fit horizontally
   .bind('testfit', function(){
      var nav = $(this),
            items = nav.find('a');
      
      $('body').removeClass('nav-menu');                                
      
      // when the nav wraps under the logo, or when options are stacked, display the nav as a menu              
      if ( (nav.offset().top > nav.prev().offset().top) || ($(items[items.length-1]).offset().top > $(items[0]).offset().top) ) {
      
         // show the menu -- add a class for scoping menu styles
         $('body').addClass('nav-menu');
      
      };           
   })
      
   ...

Finally, we'll assign a click event to the heading element to show/hide the menu options.

$('.nav-primary').find('h3')
   .bind('click focus', function(){$(this).parent().toggleClass('expanded')});

We now have a menu that is responsive to differences in screen size (view the demo):

Examples of the closed menu and with all options visible, sized for the tablet screen, and displayed as a horizontal bar next to the logo

..and text size:

On larger screens, the navigation becomes a compact menu when text size is increased and the nav no longer fits on one line

Keep the conversation going

The pattern discussed here is one possibility for coding a responsive navigation list. We hope to discover more, and will update our RWD-Nav-Patterns git repository as we build additional examples.

This demo code is open source and available for download. Feel free to put it through its paces. If you use it and see room for improvement, please submit a pull request and we'll review it as soon as possible.

Book cover: Designing with Progressive Enhancement

Enjoy our blog? You'll love our book.

For info and ordering: Visit the book site

Comments

How timely! What a great write-up. I just did a round-up of responsive navigation techniques on my blog: http://bradfrostweb.com/blog/web/responsive-nav-patterns/ It’s great to see step-by-step looks at these approaches. Looking forward to Part 2!

Comment by Brad Frost on 02/28  at  12:08 AM

Navigation in a responsive layout is something I’ve been meaning to look at in more detail. I know Brad Frost has examined different solutions in creating site navigation in a responsive layout. http://bradfrostweb.com/blog/web/responsive-nav-patterns/

I plan to take a close look at this post and Brad’s post once I get some free time. Looking forward to your follow up post on this topic.

Comment by Brett Jankord on 02/28  at  01:15 AM

Wow.  Really outstanding. thank you. Maybe I didn’t need to by O’Reilly’s Mobile Web LOL.

Comment by Ann Richmond on 02/29  at  09:53 AM

A few nitpicks/questions:
- why a :hover style for anchors but no :focus and/or :active?
- do you consider having the skip link come back onscreen on :focus for sighted keyboarders, or do you use a separate solution on a real page for them? (of course those using a webkit-based browser get zero keyboarding benefit from skip links but everyone else does...)
- why force the user’s body font size to almost half what they’ve got set as their default? It seems to cause you to need to set all your font sizes to “ginormous” just to make anything readable. I mean the dreaded “62.5%” thing. Why not start at “100%” and use “1em” as your base, and go from there?
- Your JS looks like it’s probably pretty small, and it’s doing little more than waiting for events and then adding/removing classes from at most 2 elements, but the code included suggests jQuery… while mobiles could be grouped as “has JS” or “doesn’t”, of the group “has”, we are still dealing with things like battery technology, wireless internet connections and limited CPU… are you using jQuery Mobile, or jQuip, or xui.js, or some other made-for-mobile light-in-downloads framework? Is it better to try to avoid having the user download a framework at all if they’re likely to be using a mobile? (even tho bandwidth and screensize don’t really have such a relationship, but we make these assumptions when other info is scarce.)

Comment by Stomme poes on 02/29  at  12:35 PM

@Stomme, you raise some great points.
- I missed the :focus styles, thanks for catching that!
- in general, setting the body size to 62.5% makes the math easier when using ems—but, to your point, that’s less true now that em-based media queries are in play, as some browsers don’t listen to the body size when interpreting them.  So, yes, we should probably move away from the habit of resetting the body size and pull out our calculators again.
- good point about JS and performance.  If this nav element is the only (or one of a few) simple JS-based components, then no, it’s not worth loading an entire library.  We tend to build complex responsive web apps and sites, which benefit greatly from having a library in place, and the use of jQuery here is an artifact of that.  If you have ideas about creating a non-jQuery version, have at it!

@Brad, I saw your post—it’s really helpful (and interesting) to see the current state of affairs with responsive nav patterns.  And thanks for the mention!

Comment by Maggie (Filament) on 02/29  at  04:50 PM

@Maggie
re calculating with em’s: easiest not to. Just assume someone’s default is 1em, and for you that might be 16px, but who cares if everything is also sized in em’s. For me, 1em is about 20px. Bad eyes. Unless images are involved, don’t worry about what it ends up as pixels: that’ll be different per user anyway.

Have you seen David Hund’s responsive menu at valuedstandards.com? It relies on :target so without some JS it won’t degrade well, but otherwise is similar to this menu.

Comment by Stomme poes on 02/29  at  06:06 PM

Oh woops, wanted to also mention:
I once tried Nicholas Zakas’ isMedia() function
https://gist.github.com/08602e7d2ee448be834c
to only load jQuery if the viewport had a minimum size, with the assumption that any JS running on smaller screens would be minimal and library-less.

Didn’t work for me as jQuery seemed to want to wait for page load event, not get called after it, but I chalk that one up to my limited JS and page-loading-events knowledge. But having the library only get called and loaded when a screen (whom I will assume then might not be on batteries, has a good internet connection and the kind of CPU found on larger internet-accessing machines) is large enough and then also call more complex scripts to do more fancy silly things… sounds like a nice thing to try.  Stephanie Rieger makes a nice point about how wrong that assumption can be but without access to lots of server-side logic and testing, as a front-ender this is something to try.

Comment by Stomme poes on 02/29  at  06:11 PM

Stomme:

Agreed. conditionally async-loading a framework is a fine approach. We employed that technique on the BostonGlobe.com site, though it doesn’t use any server detection to do so.

btw - If you need a lightweight matchMedia api polyfill, you might try this https://github.com/paulirish/matchMedia.js

Comment by Scott (Filament) on 02/29  at  06:33 PM

@Stomme, thanks for sharing your thoughts on font sizing in the CSS.  We’ve found that the best solution is the one that best supports the design and development of the project.  Sometimes that involves resetting the font size, sometimes it doesn’t.  Our primary goal is always to make sure that the page is legible, usable and accessible on the widest range of devices.

To Scott’s point, we’re very big on making sure assets are loaded in a smart way.  I intentionally didn’t include that in this simple code example to keep the focus on how to construct the nav.

And thanks for the reference to David Hund’s menu, I’ll check it out.

Comment by Maggie (Filament) on 02/29  at  06:46 PM

What we did (and admittedly this might not work on all websites) was this. First, style nav links as 44x44 icons with tiny print beneath: as it turns out, this looks good on *all* screen sizes and is easy for CSS. Next, rethink what the user *needs* on each page, and provide only those nav links. Eliminate the need for dropdowns. On the home page, for example, provide only the dropdown “headers” (main topics). Once a topic is chosen, on its page, show a “home” link plus links to other pages in the same topic (plus, optionally, contact). Great bandwidth savings, too.

Comment by fjpoblam on 03/03  at  06:25 PM

@fjpoblam, generally speaking, mobile users want access to all content (and nav options), just presented in a usable format for their device.  Limiting what’s available to them on mobile could lead to a frustrating experience.

Comment by Maggie (Filament) on 03/05  at  06:36 PM

Very impressed with what you’ve done with this blog post.

I’m in the process of working with a client for a solution to a deep level of navigation and hope that I’ll be able to feed back some experiences to you.

Looking forward to future write ups.

Comment by Justin Avery on 03/07  at  09:15 AM

@justine

For deep linked menu you can integrate something similar to this, it will be really friendly and classy on mobile device

http://filamentgroup.com/lab/jquery_ipod_style_and_flyout_menus/

Comment by Yoosuf on 03/10  at  03:15 AM

Commenting is closed for this post.

Book cover: Designing with Progressive Enhancement

Enjoy our blog? You'll love our book.

For info and ordering: Visit the book site