Setting the current menu state with CSS

Every now and then I get an email from someone asking me how to use CSS to set the current state for a menu bar. Both the Inverted Sliding Doors tabs technique that I wrote about last summer and the navigation bar on this site, described in Turning a list into a navigation bar, use CSS to set the current state.

It isn’t very complicated and has been described elsewhere, but to have somewhere to refer people asking about it I’ve written a quick explanation of how it works.

It’s really quite simple, and I’ll use this site as an example. There are a few main sections on the site: Home, About, Archive, Lab, Reviews, and Contact. These sections make up the top menu and are marked up as an unordered list, with each list item given a unique id:

  1. <ul>
  2. <li id="nav-home"><a href="/">Home</a></li>
  3. <li id="nav-about"><a href="/about/">About</a></li>
  4. <li id="nav-archive"><a href="/archive/">Archive</a></li>
  5. <li id="nav-lab"><a href="/lab/">Lab</a></li>
  6. <li id="nav-reviews"><a href="/reviews/">Reviews</a></li>
  7. <li id="nav-contact"><a href="/contact/">Contact</a></li>
  8. </ul>

The body of every document is then given an id that reflects which section it belongs in: <body id="home">, <body id="about">, <body id="archive">, <body id="lab">, <body id="reviews">, and <body id="contact">.

Note: if you look at the source of this site you’ll find that I’ve used class instead of id. That’s because the id is used for my CSS signature. It doesn’t really matter which you use as long as you’re consistent.

With the markup in place, you need to write one or more CSS rules to match the body id with the correct list item id. Again, using the navigation bar of this site as an example, the styles are applied to the links within the list items:

  1. #home #nav-home a,
  2. #about #nav-about a,
  3. #archive #nav-archive a,
  4. #lab #nav-lab a,
  5. #reviews #nav-reviews a,
  6. #contact #nav-contact a {
  7. /* declarations to style the current state */
  8. }

You may need an additional rule to style the :hover state of the current item. In this case the selectors would look like this:

  1. #home #nav-home a:hover,
  2. #about #nav-about a:hover,
  3. #archive #nav-archive a:hover,
  4. #lab #nav-lab a:hover,
  5. #reviews #nav-reviews a:hover,
  6. #contact #nav-contact a:hover {
  7. /* declarations to style the hover effect of the current state */
  8. }

That’s all there is to it. The above rules only take effect when the body id matches that of a list item. Otherwise nothing happens.

This is a nice and simple trick that’s useful when you can’t use server side logic to insert a class or an id into the list item that holds the current menu item.

Posted on March 3, 2005 in CSS

Comments

  1. (Your CSS signature is a bit useless as you are styling the HTML element as well and CSS does not know the concept of selecting ancestors.)

  2. Clever, I never thought of doing it that way. I always use server-side logic to insert a class=”selected” or similarly instead.

    This explains why I so often see people putting unique id’s on the li tags of the navigation as well, I’ve always wondered why some people do that.

  3. March 3, 2005 by Roger Johansson (Author comment)

    *slaps forehead* Thanks Anne. I added the CSS signature before implementing content negotiation, which forced me to apply the background to the html element. I completely forgot about the consequences. Anyway, you should now be able to override the text colour. Not the background, unfortunately.

  4. While it’s nice to be able to do this (setting current state) exclusively with CSS, it doesn’t make sense to me from a logical perspective. Why should the user be given a link to the page they are on? With just a teeny little bit of server-side code, you can deliver your nav list without the anchor around the current item. Seems cleaner to me.

    Ah, CSS signatures. Back in January, Dave at mezzoblue published an article in which he talked about giving pages specific classes. At the time, I had no idea why you would give your page (body) a class as opposed to an ID. It all makes sense now, very cool.

    Anne, the CSS signature is for you to customize this site (via user stylesheets). You can put rules in your stylesheet to target this site exclusively.

  5. Doh, didn’t get that at all. Sorry Anne. I’m obviously new to this whole CSS signatures thing (didn’t even understand what you were saying).

  6. March 3, 2005 by Roger Johansson (Author comment)

    Justin: Sure, server side is the best way of doing it. This is for situations where that isn’t possible, for whatever reason. Why am I using it here then? Laziness.

    Re CSS signatures: I thought about it some more and added a couple of rules to “userContent.css” in my Firefox profile. Turns out me styling the html element isn’t actually a problem here since most, if not all, pages have enough content for the body element to cover the entire height of the viewport.

  7. The other thing I noticed is that it does make sense (in your scenario) to have the current state clickable because is simply telling the user that they are in X section of this site, not exactly on X page.

    Take this article for example, which is within the “Archives” section of the site. However, it is not the main Archive page, so the link to “Archives” should be clickable so that I can navigate back to the main section page.

  8. The nice feature of using the class attribute is that you can handle multiple hierarchy levels. Say the menu actually displayed the sections including “archive” and then under “archive” it displayed “2005”,”2004”, and “2003”.

    Now you can set the class attribute to: class="archive 2005" and then you can highlight both the “archive” menu option as well as the “2005” menu option.

  9. However, a caveat would be that the ID properties used on the “submenu” options be unique across menu sections obviously. Otherwise, multiple submenu options could be indicated as active. Thus, in my example, it would make more sense for each of the year options to be “archive-2005”, “archive-2004”, etc.

  10. One problem with this approach has, of course, to do with IE. If you have submenus, the rule #home #nav-home a will apply to all descendant anchors. What you would ideally like to use is #home #nav-home > a to only target direct descendants. Of course IE doesn’t support the child selector. I had this same problem on my page, and had to later override things with !important to get them working properly.

  11. Clever technique. I usually use a server technique that writes out a ‘class’ (current or selected) on the ‘li’ that is the current menupoint. I have also started to add from the server side ‘em’ to the li text to show what menupoint is selected for non-graphical browsers. It’s just a way of making it more accessible. Another way is to add an arrow or something else to hint what is selected.

    Look here for two screenshots from lynx.

    First only styling in css

    Second I have use ‘em’

    Third is an option with an arrow instead.

    Using strong is the same as the first one, no visual change at all for non-graphical browsers.

  12. Good Read.

    I call this technique “CSS conditional selectors”.

    Not that clever, I know.

    I recently wrote conditional selectors for three levels of navigation. ( www.idacomm-bpl.com )

    Keep in my this technique can become very complex. For Example here is a conditional selector from idacomm-bpl.com:

    body.chrisb #navtwo #mnav #navfour #chrisb a, body.bobs #navtwo #mnav #navfour #bobs a, body.daveb #navtwo #mnav #navfour #daveb a, body.brucem #navtwo #mnav #navfour #brucem a, body.mattc #navtwo #mnav #navfour #mattc a, body.cameronc #navtwo #mnav #navfour #cameronc a, body.jeffreys #navtwo #mnav #navfour #jeffreys a, body.darrela #navtwo #bodnav #navfour #darrela a, body.janp #navtwo #bodnav #navfour #janp a, body.bryank #navtwo #bodnav #navfour #bryank a, body.joans #navtwo #bodnav #navfour #joans a{ color:#333333; background: #FFFFFF url(none); text-decoration:none; }

  13. March 4, 2005 by Derek Collins

    I’m curious as to how this technique would work if you also wanted to show which sub-section you were in. If you were indicating which top level section you were in in the main navigation (the top navigation, for example), how would you indicate which sub-section you were in (the side navigation, for example)? What if you had 3 levels of navigation?

    I’m also curious to learn more about how this is being done server side. Could anyone point me to some resources? Thank you in advance!

    I found this technique very useful when you first wrote about it in “Turning a list into a navigation bar.” Thank you for continuing to write very useful articles!

  14. Derek,

    I know there are resources out there, don’t know where I’ve seen it though. If you are comfortable with server-side programming, it just a matter of retrieving the active page name (URL) and outputing your nav list based on this value. I know most people around here are PHPers, a language I know very little about. I could help you if you use something like ASP/ASP.NET.

  15. March 4, 2005 by Derek Collins

    Justin,

    Thanks for your reply. Your suggestion is basically what I was looking for (retrieve the current URL and create your nav and sub-nav based on that). I am most comfortable with PHP, but I appreciate your offer.

    Also, how would you use your CSS method for main navigation and sub-navigation? Do you think that would be a good strategy? I started to think of adding an ID or class to the page title and sub-title (h1 and h2 perhaps) and then building out the CSS the way you described but for these other elements. I could see this getting messy pretty quick on a large site, however. Therefore I think doing this server side would be a better option. What do you think?

    Does anyone else know of (PHP) resources that discuss creating navigation server side in this way?

    Thanks!

  16. March 5, 2005 by Roger Johansson (Author comment)

    Justin: Leaving the link does make sense for anything that’s in the archive section, but not for “Home”, “About”, and “Contact”, since those are just single pages.

    Michael: You could also add more rules to take care of sublevels:

    #home #nav-home #sub-home a
    

    Jens: Replacing the anchor with em (or strong) is a good idea. Hmm. I have a vague memory of reading something about that somewhere…

    Cody: Yes, if you have multiple levels this can become pretty complex. I’ve never used it for anything but a top level nav.

    Derek: Sorry, I don’t have any links to php resources handy. I’ll see if I can dig anything up. I normally just check which link is the current page (or a parent of it) and add a class to those list items.

    Doing the work server side is better. This is for situations where that isn’t an option.

  17. sorry for doing so but i’d like to push the discussion into another direction: the different interpretation of the pseudo-states of an anchor point - if my semi-prof-hobby understanding/coding skill is correct - IE and Firefox do a strange mistake when using TAB to skip through anchor-points [:focus and :active become mixed up] (for example: watch what happens when TABbing through the navigation of this site using IE -> highlighting with orange / in Opera it isnt even possible to do so, what can be fixed assigning a tabindex to anchor points - with all of the disadvantages of this technique (anchors background-color gets blue)…

    if the navigation works with background-colours its not that dramatic, but when background-images come into play the result of this “mixing up the states” gets a little weird.

    has someone a technique to solve this?

    is my implementation (example) of the “other states” correct or is there still unneccessary code in my css?

    great article! -> sure find its way into “my” code :D

  18. (Note that since you are using XHTML 1.0 Strict the ID attribute is allowed on the HTML element.)

  19. sucks a tooth Roger, I’m sorry, I just don’t see the point in doing it this way, unless you have a LOT of stuff that is page dependant.

    Let’s look at this objectively. If you have a main level navigation with about 6 - 10 links, you have to write all this code to do something that would be easier done with PHP, or ASP, or your favorite scripting language.

    I think a solution like this only works when the pages are static, i.e yourdomain.com/about.php(html) instead of yourdomain.com/index.php?pageid=23 - In fact, even if you’re using apache to get friendly urls, this is still possible. Wouldn’t it be easier to do something like:

    <a href="theurl">thetitle</a>

    Then:

    if($pageid == $row[page_id]) $class = “active”;

    If that’s the case, you really only need two:

    nav ul li a { /* css */ } nav ul li a:hover, #nav ul li a.active {

    /* declarations to style the current state */ }

    I don’t mean to chuck you a wobbly, but it seems a little more work than needed, doesn’t it?

    -Ryan

  20. March 7, 2005 by Roger Johansson (Author comment)

    Ryan: Absolutely. If server side scripting is a possibility it’s the way to go, and that’s the way I normally do things. No argument from me on that.

  21. March 10, 2005 by Derek Collins

    If anyone is interested I found a nice article on A List Apart that describes using PHP to set the current state of a navigational element.

    Article: Keeping Navigation Current with PHP

    Its clear that this post was intended for those who don’t have the option of using a server side technology (or don’t want to), but since some of the comments started to go the direction of using server side technologies, I figured it was appropriate to add this resource to the discussion for those who want to learn more about the server side route.

    I hope this helps!

  22. March 10, 2005 by Mobo

    is there an SSI route for this?

  23. Thanks for the article, and the link to the ALA stuff about PHP Includes. Just starting to learn a tiny bit about PHP, so very useful….:)

  24. This is what i need, i’d been looking for this trick.

    Thank you so much :) , i am gonna use it.

  25. After lots of googling, this is the first page that is even remotely useful to me. I can’t use server-side scripting, so I’m stuck with CSS and JavaScript. I also try to make my code as abstract as possible. That’s why I’d like to keep my menu free of link-dependant classes/id (like nav-home, nav-about, etc). But I still want to set a special class for the current page. Is there a way to accomplish this with CSS+JS? And in XHTML 1.0 Strict, if I’m not asking too much?

  26. March 31, 2005 by Roger Johansson (Author comment)

    vex: How about using nav-1, nav-2 etc for the class/id names?

  27. Roger et al.: One of the cool things about this technique of assigning page-specific classes to body (or any other parent/container of the nav list) is that it’s easy to code it both server-side and client-side using essentially the same logic. I use this on novitskisoftware.com to show/hide ‘pagelets’ of content on the home page. If javascript is enabled, it sets the body.className; if not, the nav menu links signal the server-side script to reload the page with the appropriate body class set.

    I love this technique because it keeps scripting to a minimum. Neither client-side nor server-side scripts have to walk or parse the nav menu or for that matter even perform much conditional logic: they just copy a value from the selected list item to the parent element class and it’s done. Our browsers contain fast, competent DOM parsers, and using CSS this way simply offloads the parsing chore onto an engine that specializes in just that.

    The only? down-side is that you have to specify all your page identifiers in your stylesheet, whereas using a simple class=”selected” as Ryan suggests lets you use a static stylesheet when you redefine your pages.

  28. July 6, 2005 by Larry Tiblis

    Does anyone know what the ASP equivalent coding would be for the php artical above. I would rather use PhP on the site I work on but the higher ups like ASP.

  29. What about PHP dynamic scripted pages that have links which call the page up…how to apply this? It only works for static pages right? Say I’m using the menu bar above, and a few of the links, say ‘reviews’ ‘lab’ and ‘archive’ are dynamic pages? How to apply?

  30. January 30, 2006 by Travis Flippen

    Your Wall Street prediction poll is flawed because the real answer is that the Wall Street stock will close up from the previous down and down from the previous up.. It’s all about perspective!

  31. Are there a pure CSS (no JS, no PHP) highlight current menu item solution for iframe please? Thank you

  32. That’s great if you aren’t using frames. What if your navigation bar was in a single frame source controlling the content in another frame? I’d assume that then you would need a script for that. Where can I find one?

  33. March 15, 2006 by Roger Johansson (Author comment)

    Kenzo and Al: If you are using frames, may I suggest you reconsider. There are too many usability problems with frames: Who framed the web: Frames and usability.

    If you still want to use frames, sorry, I have no idea where to find a script to keep them in sync. Besides, that will only work if the user has scripting enabled. You’ll need to provide a fallback for those who do not.

Comments are disabled for this post (read why), but if you have spotted an error or have additional info that you think should be in this post, feel free to contact me.