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.

Advantages
- No JavaScript
- No extra HTML
- Simple CSS
Disadvantages
- Reserves too much screen-space
Demo
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.

Advantages
- Doesn’t need much space
- Uses native controls
Disadvantages
- Needs JavaScript
- Duplicate content
- Select is not styleable in every browser
Demo
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:
- 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.
- 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.


Advantages
- Doesn’t need much space when closed
- Custom styling
- No JavaScript
Disadvantages
- Bad semantics (input / label)
- Extra HTML
Demo
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.


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
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.
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.
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.
@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.
@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.
@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.
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!
Some good old trial and error for many hours on various platforms with the help of BrowserStack. I just wanted it to work everywhere.
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.
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 aclass
) would be more appropriate.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!
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.
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/
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 theinput
(checked / not checked). And if you want to fix the bug, you could add an emptyonclick
onto thelabel
or place theinput
(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 hiddenlabel
for SEO, but it removes the need to have that emptyonclick
.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).@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.
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.
Very nice guys, thank you.
Glenn
Awesome, I’m always learning, THX a lot
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.
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.
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.
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.
Nice write up Tim!
As Dave wrote above, this article is a good reminder to keep in mind when creating a new responsive menu.
Is it possible to add sub menus?
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.
It is interesting and useful.
Awesome Menu’s. :)
Is there a way for that scss to be converted in just normal css.
Hey there, If you click where it says SCSS in codepen it will compile into CSS.
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
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”.
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?
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!
Great write-up Tim! And thanks again for your help with the navigation menu plugins I’ve been working on.
Perfect categorization and comparison , just as always!
Thanks!
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.
It probably would have been good to include the word ‘mobile’ in the title of the post.
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?
How would you go about making a menu which uses hover for dropdown on PCs, and clicks on Mobile?