A Flexible Approach to Responsive Navigation
Many responsive websites provide a horizontal navigation bar on large screens and drop-down navigation for smaller viewports. It's a perfectly decent approach, but it's not without its issues. Firstly, devices don't just come in large and small; they come in every size imaginable. Secondly, the navigation might well change over time. Thirdly, the layout or font size might vary across screen sizes. We're going to do things differently…
Why not check (with JavaScript) to see how much of our the navigation actually fits in the space available? Once we know that, we can take items that don't fit within the navbar and move them into a dropdown menu. On especially small screens, we can choose to put the entire menu into a dropdown.
Does the Navigation Fit?
Which type of navigation works best? The answer to this question depends on several factors, including screen size, font size, font family, number of nav items, and the context in which the navigation appears. These factors interact with one another, and they can all change.
Rather than considering each of these factors, we'll simply look at the end result: do all the navigation items fit in one neat row, or do some get pushed down to the next line? If it's the latter, how many items do fit? Armed with this information, we can choose the right navigation for the job.
Step 1: Choosing Breakpoints
Responsive design has two main features. Firstly, the design must be fluid: its width scales with the width of the browser. Secondly, there are breakpoints, widths at which the design changes through use of CSS media queries. For example, you might decide that when the browser is no wider than 480 pixels, a sidebar will move below the content and and headings will become smaller. You can have as many or as few breakpoints as you'd like.



You can create a breakpoint by using a media query, like this:
1 |
@media only screen and (max-width: 480px) { |
2 |
/* This CSS will be applied only to phones and other small devices. */
|
3 |
}
|
So, how do we go about choosing breakpoints? One approach is to pick a few widths that match up with common device sizes.
Twitter Bootstrap has several breakpoints, including a "smartphone" breakpoint that is triggered on viewports no wider than 480px. It also has a "portrait tablets" breakpoint that is triggered between 768px and 979px.
You may notice that there's no "landscape tablet" breakpoint in Bootstrap. That's because the iPad is 1024 pixels wide in landscape - the same width as many desktop screens. That brings us to the main flaw with this approach: it involves making educated guesses about types of devices. A device that hits our "portrait tablet" might actually be a really big phone or a really small laptop.
Media queries don't tell us what type of device is being used, but we can make an educated guess based on the device's size.
An alternate approach would be to base breakpoints on the design and content. You could implement a fluid version of your design with no breakpoints, and then test it at different widths. When you encounter widths where things start to look strange, it's a good time to consider adding a breakpoint. This is the approach favored by Ethan Marcotte, who coined the term "responsive design."



Which approach is best? As with so many things in life, it depends. Are you using a pre-built framework like Bootstrap? Do you think it'll be easier to explain responsive design to your team or to clients if you can talk about specific types of devices? If you answered "yes", choosing breakpoints based on device sizes may be right for you. If you answered "no", choosing breakpoints based on your design and content may give you more freedom and flexibility. That's the approach that we'll take in this tutorial.
Step 2: Writing the Markup
Let's start building a simple page. We'll build an "article" area that contains some content and a navbar. Next to the article, we'll place an aside.
1 |
<article>
|
2 |
<nav>
|
3 |
<ul class="navbar" id="mainNavbar"> |
4 |
<li><a href="/">Home</a></li> |
5 |
<li><a href="/products.html">Products</a></li> |
6 |
<!-- More nav items... -->
|
7 |
<li><a href="/contact.html">Contact</a></li> |
8 |
</ul>
|
9 |
</nav>
|
10 |
<p>Here is our content.</p> |
11 |
</article>
|
12 |
<aside>
|
13 |
<p>Here is a side note about our content.</p> |
14 |
</aside>
|
Include ten or so navigation items in this list, for the sake of the demonstration.
Step 3: Basic Styling
First, let's style our list to look a navigation bar.
1 |
.navbar { |
2 |
background-color: #055; |
3 |
margin: 0; |
4 |
padding: 0; |
5 |
width: 100%; |
6 |
line-height: 1; |
7 |
overflow: hidden; |
8 |
}
|
9 |
|
10 |
.navbar li, .navbar a { |
11 |
display: inline-block; |
12 |
}
|
13 |
|
14 |
.navbar li { |
15 |
list-style-type: none; |
16 |
}
|
17 |
|
18 |
.navbar > li { |
19 |
margin-left: .25em; |
20 |
}
|
21 |
|
22 |
.navbar > li:first-child { |
23 |
margin-left: 0; |
24 |
}
|
25 |
|
26 |
.navbar a { |
27 |
padding: .25em; |
28 |
text-decoration: none; |
29 |
height: 1em; |
30 |
font-weight: bold; |
31 |
color: #fff; |
32 |
}
|
33 |
|
34 |
.navbar a:hover { |
35 |
background-color: #088; |
36 |
}
|
We'll also make our article stand out a bit, and display our aside as a sidebar. We're using percentage values for our widths to make our design fluid.
1 |
body { |
2 |
font-family: sans-serif; |
3 |
font-size: 16px; |
4 |
background-color: #fff; |
5 |
}
|
6 |
|
7 |
article { |
8 |
background-color: #eee; |
9 |
padding: 2.5%; |
10 |
margin-right: 1%; |
11 |
width: 64%; |
12 |
float:left; |
13 |
}
|
14 |
|
15 |
aside { |
16 |
background-color: #ccc; |
17 |
padding: 2.5%; |
18 |
width: 25%; |
19 |
float: left; |
20 |
}
|
Our end result should look something like this:

Step 4: Add a Breakpoint
When our width gets particularly small, the text wraps so much that it becomes difficult to read. We can fix that by letting both the article and the aside take up the full width, and pushing the aside down below the article.
1 |
@media only screen and (max-width: 550px) { |
2 |
aside, article { |
3 |
width: 95%; |
4 |
float: none; |
5 |
}
|
6 |
|
7 |
article { |
8 |
margin-right: 0; |
9 |
margin-bottom: 1em; |
10 |
}
|
11 |
}
|

Feel free to play with the window width and watch what happens. In general, as the window gets smaller, there's space for less and less of the navigation. When you shrink the window to 550px, though, you actually gain some space for the navigation as the aside drops to the next line.
The number of items that we can display in the navbar depends on several factors, including the width of the window, the breakpoint, the font size, and the number of items in the navigation.
Step 5: Set a Fixed Height on the Navbar
In many cases, our navbar content doesn't fit on one neat line. Let's fix that!
If the user has JavaScript, we'll hide items that don't fit. We'll show them in a dropdown menu later. If the user doesn't have JavaScript, we'll just let the navbar's height expand as needed. It's not pretty, but it's at least functional.
To do this, we'll download and include a custom build of Modernizr, a JavaScript library that allows us to test feature support in browsers. Our custom build tests for JavaScript support and touch event support.
Next, we'll add a "no-js" class to our tag. If the user has JavaScript, Mozernizr will change that class to "js." If the user doesn't have JavaScript, the class will remian "no-js".
1 |
<html class="no-js"> |
2 |
<script src="modernizr.custom.js"></script> |
Note: For performance reasons, it's usually best to avoid putting scripts at the top of the page. While the browser is busy downloading scripts, it will stop downloading other files and rendering the page. In this case, that's actually what we want: we don't want the browser to display anything until our .js class is in place. There is a small performance hit associated with this approach, which we've mitigated somewhat by using a custom build of Modernizr. If you just need that "no-js" class and don't plan to use the other features of Modernizr, you could make your page even lighter by using this one-line wonder instead.
Now that we have a working .js class in place, let's use it to hide any items that wrap to a second line.
1 |
.js .navbar { |
2 |
height: 1.5em; |
3 |
overflow: hidden; |
4 |
}
|
Step 6: Add FlexMenu
Next, let's download flexMenu, a jQuery plugin that lets us check whether all items fit in the navbar and display the appropriate type of menu. We'll also need jQuery itself. Put these scripts at the bottom of the page, so that they don't block the page from displaying while they load.
1 |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script> |
2 |
<script src="flexmenu.min.js"></script> |
We also need to tell flexMenu to look at our menu and resize it as needed:
1 |
<script type="text/javascript"> |
2 |
$('#mainNavbar').flexMenu(); |
3 |
</script>
|
Now, as we resize the page, a "more" link appears when some of the items don't fit on the navbar. You can hover over or tap this link to display a dropdown with these items. When the page gets too small for more than one or two items to show in the navbar, we're presented with a "menu" link. When you hover over or tap this link, all the navigation items appear.

We now have the behavior that we want, but the dropdown menu looks pretty awful. That's okay - we'll fix that in the next step.
Step 7: Style flexMenu
Now, let's add some styles to make the dropdown look nicer. We'll also add a CSS arrow next to the "menu" or "more" link to make it clear that these links will open a drop-down menu.
With CSS arrows, there's no image to load, and the arrows stay sharp on high-resolution displays. This approach works in all modern browsers as well as IE8. If you need to support IE7 or earlier, be sure to include an image-based fallback.
1 |
.flexMenu-popup { |
2 |
padding: 0; |
3 |
background-color: #088; |
4 |
margin: 0; |
5 |
}
|
6 |
|
7 |
.flexMenu-viewMore > a, .flexMenu-viewMore > a:hover { |
8 |
background-color: #5599AA; |
9 |
}
|
10 |
|
11 |
.flexMenu-viewMore > a:after { |
12 |
display: inline-block; |
13 |
content:""; |
14 |
border-left:0.3em solid transparent; |
15 |
border-right:0.3em solid transparent; |
16 |
border-top:0.4em solid white; |
17 |
margin-left:0.4em; |
18 |
position: relative; |
19 |
top: -.1em; |
20 |
}
|
21 |
|
22 |
.flexMenu-viewMore.active > a, .flexMenu-viewMore.active > a:hover { |
23 |
background-color: #088; |
24 |
}
|
25 |
|
26 |
.flexMenu-popup > li > a, .flexMenu-popup > li { |
27 |
display: block; |
28 |
}
|
29 |
|
30 |
.flexMenu-popup > li > a:hover { |
31 |
background-color: #3aa; |
32 |
}
|



Step 8: Adjust for Touchscreens
Now we have a toolbar that works well on a variety of screen sizes, but it's a little difficult to use on a touchscreen. Touchscreens don't provide as much precision as mice and trackpads, so it's easy to miss and tap the wrong item. To fix this problem, let's make the links a little larger and move them a bit further apart.
Modernizr, which we added earlier, provides a CSS class for each feature that it checks for. On devices with touchscreens, it will add the class "touch." On devices without touchscreens, it will add the class "no-touch".
1 |
.touch .navbar { |
2 |
font-size: 1.25em; |
3 |
}
|
Because we've expressed all our sizes in ems, we can scale the whole navbar up or down by its font size. The one line above (ok, techincally three the way we've spaced it) adjusts not only the size of the text but also the size of the arrows, the navbar's height, the padding on the links, and the margin between items.
Note: The .touch class indicates support for touch events, which lets developers build touch input into web applications. Devices that have both touchscreens and mice (e.g., Windows 8 slates) will most likely get the .touch class. Most touch devices simulate mouse events for compatibility purposes, so there's currently no way to use Modernizr to detect devices with both touchscreens and mice.



Step 9: Fix Cross-Browser Funkiness
If you pull up the demo so far in IE7, you'll probably notice two problems:
- As you resize the window, the sidebar sometimes gets pushed below the article before we hit our breakpoint.
- The drop-down menu is one pixel below the bottom of the "more" link, so the menu disappears when you try to hover over it.
The first problem is due to IE7's handling of percentages. If it calculates a value that inclues a fraction of a pixel, it always rounds up. The second problem is probably caused by a similar issue.
To work around this issue, we can set a fixed width for our page in IE7. To do this, let's first add a "less than IE8" class at the top of our page.
1 |
<!--[if lt IE 8]> <html lang="en-us" class="no-js lt-ie8"> <![endif]-->
|
2 |
<!--[if gte IE 8]><!--> <html lang="en-us" class="no-js"> <!--<![endif]--> |
Next, let's add some CSS that sets a specific pixel width for the article and aside in IE7. The total width of these areas, plus the padding, margin, and allowance for rounding up, comes out to 901 pixels.
1 |
.lt-ie8 .flexMenu-popup { |
2 |
margin-top: -1px |
3 |
}
|
4 |
|
5 |
.lt-ie8 article { |
6 |
width: 600px; |
7 |
}
|
8 |
|
9 |
.lt-ie8 aside { |
10 |
width: 200px; |
11 |
}
|
12 |
|
13 |
.lt-ie8 body { |
14 |
width: 901px; |
15 |
}
|
You also might notice that on iOS, the zoom level changes when you rotate the device from portrait to landscape. To fix this problem, just grab this JavaScript-based fix and reference it at the bottom of the page. This issue has apparently been fixed in iOS 6.
Further Reading
Brad Frost's articles Responsive Navigation Patterns and Complex Navigation Patterns for Responsive Design cover a wide variety of approaches to navigation on responsive sites.
Luke Wroblewski is also a great resource for responsive and mobile design patterns. In particular, his Off Canvas approach is pretty clever: it places navigation and other secondary content offscreen on small devices. When the user taps a button, the content slides in from the left or the right.
These resources don't provide an exhaustive list, of course. Responsive design is a still a young field, and you can expect loads of cool new design patterns to appear in the future. Maybe some of them will be yours!