Build a smart mobile navigation without hacks

Build a smart mobile navigation without hacks

  • Knowledge needed: Basic HTML, intermediate CSS
  • Requires: Text editor, devices and/or emulators at your discretion
  • Project time: 30 minutes

Aaron Gustafson explains how to build a progressively-enhanced, CSS-based dropdown that works as part of a responsive design

Figuring out the best way to optimise navigation for mobile devices is downright challenging. Our community has come up with a ton of different approaches for addressing this issue, each with its own set of pros and cons.

In the past, I’ve frequently advocated for converting traditional list-based navigation into a select for mobile (see example below). It’s a simple concept that is easy to implement and I love the elegance of the approach, but the fact that you need to rely on JavaScript to make it usable never really sat well with me. Being an ardent advocate of progressive enhancement, I knew there had to be a better way.

select-based navigation on Retreats4Geeks.com
select-based navigation on Retreats4Geeks.com

I have always been enamoured of pure CSS drop-downs, but they have traditionally relied on :hover. And hover, as you know, is irrelevant in touch-based scenarios, so that seemed like a dead end. But then it dawned on me: :target was the answer to my prayers.

As you probably know, the target pseudo-class selector (:target) allows you to apply styles to elements referenced in the fragment identifier of a URL. For example:

  1. http://website.tld/my-page/#content

In this URL, the fragment identifier is “content”, which will make the browser scroll to bring an element with an id of “content” into view. Easy-peasy. We can then apply styles to that element when it is targeted, using :target:

  1. :target {
  2.   background: #F6FD86;
  3. }
target
An element highlighted using :target

When Nichols College approached my company about redesigning their prospective student microsite, the project presented the perfect opportunity to test out my theory about :target.

The design

The microsite is really only two pages – a form for collecting information about a prospective student and a thank you page that assembles relevant information about Nichols College’s programs based on her responses – but it includes a plethora of navigation links to help her do things like schedule a visit, begin the application process, or get directions to the school. In addition to those critical navigation pieces, the site also features a collection of navigation links that provide access to more of what I’d call “flavour” about the school.

It was a lot to juggle, but we started with the content, naturally, organising the page so users had immediate access to the stuff they were actually there to read and/or engage with before presenting them with any navigation:

  1. Site Banner
  2. Content
  3. Calls to action
  4. Contact mechanisms
  5. Links to specific school pages
  6. Social networks
  7. Search
  8. Copyright, etc.

This approach fits in perfectly with Luke Wroblewski’s “mobile first” mantra and the idea of presenting content up front and then allowing a user to pivot from there. This content-first approach led to our media query-less baseline layout, directed at feature phones.

Content first in Opera Mini
Content first in Opera Mini

As we’re all too aware, on small-screen devices, real estate is at a premium, so we were constantly looking for opportunities to streamline the experience for our users. Being that smartphones tend to have better CSS support than feature phones, we decided to tuck the less important links behind a menu icon, exposing them only when it was clicked, very much like the old CSS dropdowns. It’s a common design pattern on the mobile web, but every implementation I’d seen so far required JavaScript. I wanted to see if I could craft an equivalent experience using only CSS (and without resorting to hacks involving a hidden form control).

The dropdown menu design for goto.nichols.edu
The dropdown menu design for goto.nichols.edu

I’ll walk through the CSS, but first I should note that the following styles were placed in a style sheet aimed at medium-width devices, starting at 19.375ems. The majority of these rules were tucked within an additional media query inside that style sheet which limited their application to widths of less than 46ems (which was wide enough to support an alternate layout for this particular nav). This isolation technique ensures the rules don’t “bleed out” unnecessarily into alternate layouts.

  1. <link rel="stylesheet" href="/c/medium.css" media="screen and (min-width:19.375em)"/>
  2.  
  3. @media screen and (max-width:46em) {
  4.   /* Rules go here */
  5. }

Let’s dig in

The first step was applying the styles to reposition the nav to the top of the page:

  1. #nav {
  2.   margin: 0;
  3.   position: absolute;
  4.   top: 0;
  5.   right: 0;
  6.   left: 0;
  7.   z-index: 1000;
  8. }

Note: I’ve removed irrelevant styles so you can focus on the important pieces.

I then set up the links to appear collapsed, except in the event that the nav was the current target:

  1. #nav a {
  2.   border-bottom-width: 0;
  3.   overflow: hidden;
  4.   height: 0;
  5.   line-height: 0;
  6.   padding: 0 1em;
  7. }
  8. #nav:target a {
  9.   border-bottom-width: 1px;
  10.   line-height: 3em;
  11.   height: 3em;
  12. }

With those rules in place, I could manually manipulate the URL to toggle the menu open and closed, but that isn’t a reasonable requirement in a real-world scenario. I needed to create a link that would target the navigation list, but I wanted to do so without adding extra cruft to the document. After all, there’s no point in an element being on the page if it's only useful in one scenario. Thankfully, an old standby was perfectly suited to the task.

“Skip to” the rescue

In the early days of the web, we often employed “skip to content” links as a means of allowing our users to skip over the site navigation and get to the meat of the page. With a content-first approach, a “skip to navigation” link is equally useful for giving users direct access to navigation without having to put it all at the top of the page. The great thing about “skip to” links is that they work by manipulating the fragment identifier of the URL and, thereby, allow us to use :target. Voila!

First, I added a “skip to” link near the top of the page:

  1. <header>
  2.   <-- Logo -->
  3.   <a id="jump" href="#nav">Site Navigation</a>
  4. </header>

Then I styled it to look like the little menu button in the upper right hand corner of our design:

  1. #jump {
  2.   background: #000 url(/c/i/nav.png) 50% 50% no-repeat;
  3.   border: 1px solid #8b8b8b;
  4.   border-width: 0 0 1px 1px;
  5.   cursor: pointer;
  6.   display: block;
  7.   padding: 0;
  8.   height: 53px;
  9.   width: 53px;
  10.   text-indent: -999em;
  11.   position: absolute;
  12.   right: 0;
  13.   top: 0;
  14. }

It’s worth noting that this link is even useful for feature phones as it allows them to jump right down to the navigation. (You can see this simple approach employed by Contents Magazine and Bagcheck. For the pros and cons, consult Brad Frost’s mobile navigation scheme roundup.)

With the toggle button in place, I had a nifty way of making the nav appear, but no way to make it disappear again.

After pondering that problem for a while the ridiculously simple solution dawned on me: include a “back to content” link at the end of the navigation list. Clicking that link would make #nav no longer the target, and the list would collapse.

  1. <ul id="nav" tabindex="0">
  2.   <!-- Nav items -->
  3.   <li id="back"><a href="#top">Back to top</a></li>
  4. </ul>

Easy enough, but that only solved part of the problem. We don’t want a user to have to hunt through a list to find a link that lets them close the dropdown; instead we want it to behave as a user would expect: tapping or clicking outside the dropdown area should collapse the nav again. Thankfully, with a little z-index juggling and a touch of clever positioning it’s possible to place the link beneath the other nav items and expand it to fill the remainder of the screen (invisibly, of course). Here’s a sampling of the relevant style rules:

  1. #jump {
  2.   /* make sure the menu button is on top */
  3.   z-index: 1001;
  4. }
  5. #nav {
  6.   /* the nav sits behind the menu button */
  7.   z-index: 1000;
  8. }
  9. #nav:target {
  10.   /* unless it&rsquo;s showing */
  11.   z-index: 1001;
  12. }
  13. #nav:target a {
  14.    /* make nav links sit up a level */
  15.   position: relative;
  16.   z-index: 1;
  17. }
  18. #back {
  19.   /* establish a positioning context for the closer */
  20.   position: relative;
  21. }
  22. #back a {
  23.   /* turn the link into a ghost */
  24.   background: transparent;
  25.   border: 0;
  26.   text-indent: -999em;
  27.   /* make it fill the screen */
  28.   position: absolute;
  29.   top: -101em;
  30.   bottom: -101em;
  31.   left: 0;
  32.   right: 0;
  33.   /* ensure it sits behind the other links */
  34.   z-index: 0;
  35. }

With that in place, the menu was fully-functional. It just needed a little refinement.

The final dropdown expanded. The invisible “back to top” link has been coloured to show how it fits into the overall layout.
The final dropdown expanded. The invisible “back to top” link has been coloured to show how it fits into the overall layout.

Housekeeping

In order to spiff up the appearance of the interface, I opted to add a simple transition to the links, allowing them to grow vertically when the nav is targeted:

  1. #nav:target a {
  2.      -moz-transition: height .25s, line-height .25s;
  3.       -ms-transition: height .25s, line-height .25s;
  4.        -o-transition: height .25s, line-height .25s;
  5.   -webkit-transition: height .25s, line-height .25s;
  6.           transition: height .25s, line-height .25s;
  7. }

With the addition of that little transition, the nav performed beautifully, but I was still a little concerned about potential style bleed. Sure, if a browser supports media queries, it probably supports :target, but just in case, I opted to preface every relevant style rule with body:not(:target) (which would only be matched if the browser supported target selection). Here’s an example:

  1. body:not(:target) #nav {
  2.   /* these styles are only applied if :target and :not are
  3.      understood (and the body is not targeted, of course) */
  4. }

And there you have it: a simple progressively-enhanced, CSS-based dropdown that works wonderfully as part of a responsive design. I hope you find this approach as handy as I do.

12 comments

Comment: 1

Thanks for sharing. Great tutorial. Indeed we adopted a similar design approach for our new responsive site at http://livelinknewmedia.com - I initially saw the idea used on the Starbucks site but for me you and they assume too much from the user. Three dashes whilst to a savvy user are obviously a navigation, I wonder what my mum would think?

Comment: 2

Great tutorial. One I really plan on digging my teeth into. Lots of great examples on how best to merge progressive enhancement and adaptive design.

Comment: 3

I downloaded the demo and tried it.

It works fine at all resolutions in Chromium but when I tried with Firefox 12.0 the navigation menu is just a black bar with no options. When I resize the window to smaller resolutions to simulate tablet devices the menu items now appear as white text and work correctly when shrunk down to cell phone resolution.

Anyone see this behavior?

Comment: 4

This is a great idea and it seems to work on most browsers I've tested it on; however, I can't get it to work on Opera Mini. Nothing happens when the menu icon is clicked. Any ideas?

Comment: 5

A lovely technique, which unfortunately fails in Opera mini. Because of Opera mini being a proxy servers (So I'll guess that it breaks in Silk too). Another approach could be to target Opera mini with JS (as you're using it already) using an if statement

if (operamini) {

}
else {

}

Which is how I got around it with my mobile navigation tutorial -

http://alwaystwisted.com/post.php?s=2012-05-14-create-a-responsive-mobil...

Comment: 6

As Stu mentioned, Opera Mini on smartphone sized devices has the “menu not appearing” issue (mainly because the proxy-ing doesn’t seem to allow :target styles in). If you actually point he browser to the URL with the fragment in it, the styles *are* loaded though; I’m looking for a fix now.

FWIW, Opera Mini on smaller devices has no issues (as the media query is not invoked and the dropdown nav is not created).

Comment: 7

hello,

it might be a place to share this dropdown menu based on :focus. I believe this should work on tablets and you may navigate from links to links with tab only. No js used, just basic CSS.
take a look : http://dabblet.com/gist/2900549

I'd be glad if that brings you new approch building dropdown menu.
GC

Comment: 8

I couldn't get a CSS only fix for Opera Mini, but with JavaScript you can detect the Opera Mini userAgent and add an operamini class to the body element, then unhide the hidden elements with the .operamini selector (using the same rules as :target).

Comment: 9

Can someone let me know how to update this navigation to include sub navigation items?

E.g


  • top level item

    • sub level item



  • top level item

  • top level item



Thanks~

Comment: 10

Hi,

It took me a while to figure out why this nav wasn't working for me. I compared this tutorial with the Nichols College example and found that in the css

#back a {} was missing height: auto;

If anyone is having problems, this might fix it.

Thanks again for the tutorial! I love the menu.

Comment: 11

Also, I added the following:

#navigation a {

display:none;
}

#nav:target a {

display:block;
}

Without these changes, my menu worked fine, but I could not select anything else on the page because the (#back a) element was overlying everything, even when the menu was inactive.

Hope this helps!

Also, I'm not a Javascript wiz. Would anyone share the script they used to make the menu work with Opera Mini? Thanks!

Comment: 12

@heathauld

Adding height: auto; to #back a { } fixed the problem of the closing link not working.

The problem with display: none; and display: block; for me was, that the transitions were'nt working. The trick here is to add the following:

#nav a {
display: block;
}

I reckon without display: block, the height and line-height properties have no effect, causing the menu to always show up.
August issue on sale now!

The Week in Web Design

Sign up to our 'Week in Web Design' newsletter!

Hosting Directory
.net digital edition
Treat yourself to our geeky merchandise!
site stat collection