Automatically highlight current page in menu via Javascript

Whenever one builds a website, one issue is always guaranteed time consuming: highlighting the current page or section in the website with a different style.

In theory, this is relatively simple, just add a .current class to the link in question.

So, if you have a menu styled from a list, you’d have:

The limitation of this method becomes obvious if you have a site with more than 5-7 pages; each page needs to have its own menu, which makes updates difficult, especially if you prefer to keep the menu in a template.

I’ve seen several solutions to this problem, but I didn’t really like any of them – so I propose something more clever.

 

A pure CSS solution

One possibility, used by A List Apart would be to assign an id to each link, so that the links above become:

Then, you’d have to set the class property for each <body> tag, i.e. <body class="link1">. In the stylesheet definition, you’d write

.link1 a#link1,
.link2 a#link2
{
	background-color: #EEEEEE;

If you’re relatively new to CSS, the above definition means that any link with id set to link1 that is a descendent of a tag (<body>) with the class .link1 will be “highlighted”.

This method is an improvement in that the menu can be written only once; however, you still have to set the class for the body tag in each page.

Generating HTML

Another solution would be to use PHP/ASPX or any other server language to generate the pages and menu items. Of course, this provides complete freedom, since you can generate the html code any way you like. Still, this method can’t be used in all circumstances. Not all sites require a server technology and making a site dynamic just for this feature would be overkill.

Javascript + CSS

I’m a little surprised that I haven’t seen a similar solution published anywhere, so I’ve thought about sharing it.

The concept is very simple: The javascript code gets the current URL. It then cycles to all links that are contained in the navigation bar (any tag with id="navbar"). If the link points to the same page (i.e. “link1.html"), the code applies a class to the link.

The code should work in all browsers that support getElementById() including IE5, Firefox & Opera. You may add compatibility for older versions of IE by using document.all in addition to document.getElementById()

Here’s the code (rewritten on Oct.11.2007):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function extractPageName(hrefString)
{
	var arr = hrefString.split('/');
	return  (arr.length &lt; 2) ? hrefString : arr[arr.length-2].toLowerCase() + arr[arr.length-1].toLowerCase();
}
 
function setActiveMenu(arr, crtPage)
{
	for (var i=0; i &lt; arr.length; i++)
	{
		if(extractPageName(arr[i].href) == crtPage)
		{
			if (arr[i].parentNode.tagName != "DIV")
			{
				arr[i].className = "current";
				arr[i].parentNode.className = "current";
			}
		}
	}
}
 
function setPage()
{
	hrefString = document.location.href ? document.location.href : document.location;
 
	if (document.getElementById("nav") !=null )
	setActiveMenu(document.getElementById("nav").getElementsByTagName("a"), extractPageName(hrefString));
}

The ony thing left to do is to call setPage() from the html page, just after the menu, with <script language="javascript">setPage()< /script>

You can make the script run without explicitly calling setPage() from the html body, by adding

1
2
3
4
window.onload=function()
{
	setPage();
}

in the .js file (Suggested by Alice). If you go this route, please test to make sure that it doesn’t interfere with other onLoad events. Also, in this case the effect will be applied only after all page elements have been loaded.

That’s all. You can download a fully working example.

Download Javascript Link Highlighter
65 replies
  1. Richard
    Richard says:

    I’ve used similar techniques on web pages.

    Just add window.onload=setPage; to your javascript and it should trigger automatically.

  2. Armand Niculescu
    Armand Niculescu says:

    You’re correct Richard; I choosed to call the setPage function from HTML so that it can execute after the navigation is loaded but before the wholepage is fully loaded, which may be useful for pages with large images.

  3. Rob
    Rob says:

    Thanks for this brilliant and very useful code – I’ve been looking for this for weeks – a great Christmas present!

    This is a brilliant tool to remote-control ssi variable menu content (for example in Contribute site menus). Allows the contributer to add any menu items using a simple list, yet they still highlight automatically – sweet!

    One extra functionality would be to switch off the current link somehow – any ideas?

  4. Carrot
    Carrot says:

    This is just what I needed. As mentioned earlier, is there a way to remove the hyperlink from the current page.

    Also, I am looking at using this via SSI, and calling the script from a .js using

  5. Ben Dalziel
    Ben Dalziel says:

    Thanks to Richard for his suggestion that ensures my pages load without errors in the absence of JS.

    Absolutely love this code; it’s saving me so much time!

    I think I found a little bug though and suggest a fix here: http://bendalziel.blogspot.com/2006/07/auto-highlighting-navigation.html

    I’m pretty new to JavaScript so any hints or corrections would be great. I return “x” just because I’m not sure what would be better to return…

    Thanks again

  6. Geoff
    Geoff says:

    Thanks for the code. It’s exacly what I was looking for. One question. How would I be able to have multiple links selected on a page, ex. have a sub menu item highlighted? Each of these would be located in different divs. Thanks

  7. Armand Niculescu
    Armand Niculescu says:

    Geoff, you can have the highlighter work simultaneously on different navigation menus, but only if they point to the same page, i.e. you have a top navigation with a link to stuff.html and a left-side navigation also pointing to stuff.html

    In this case, in the setPage() function, duplicate the last line for the new div:

    if (document.getElementById('navbar')!=null)
        setActiveMenu(document.getElementById('navbar').getElementsByTagName('a').extractPageName(hrefString));
    if (document.getElementById('navleft')!=null)
        setActiveMenu(document.getElementById('navleft').getElementsByTagName('a').extractPageName(hrefString));
    }
    

    It can get however a little confusing, especially if you have links to documents with the same name, e.g. index.htm but placed in different folders, e.g. /work/index.htm and /home/index.htm. To cope with this, I rewrote the extractPageName() function but I want to test it more before i publish it.

  8. PDawg
    PDawg says:

    I’ve used this script on a previous project. Very cool and useful script. One question that I have is how do I get a main menu item to remain highlighted with I click on a sub-menu item? So, for example, “products” is the main menu item and “iPods”, “Cameras”, and “TVs” are my sub-menu items. What I would like to see is for “products” to remain highlighted while I click on “cameras”.

  9. Ahmed G
    Ahmed G says:

    Great script thanks! And if some of the pages in your navigation have query strings, then substitute the extractPageName function with:

    function extractPageName(hrefString)
    {
    var arr = hrefString.split(‘.’);
    // Check to see if the url has a query string by searching for the question mark
    var hasquestion=hrefString.indexOf(‘?’);
    if (hasquestion == ‘-1′)
    {
    arr = arr[arr.length-2].split(‘/’);
    return arr[arr.length-1].toLowerCase();
    }
    else
    {
    return arr[arr.length-1].toLowerCase();
    }
    }

    This will allow your links like filename.php?querystring=blabla to be highlighted as well. Well, it works for me anyway.

    Cheers

  10. Armand Niculescu
    Armand Niculescu says:

    @PDawg,

    You can highlight a parent’s link if the menus are nested lists (like http://www.htmldog.com/articles/suckerfish/example/), but I can’t give you very specific directions as I don’t know your exact configuration.

    Anyway suppose you have a hierarchy like
    ul#menu>li>ul#submenu>li>a

    If you look in the javascript code, you’ll see a line
    arr[i].className = 'current';.

    From there, you apply the ‘current’ class to the link itself (the 'a' tag) but you can also apply it (or some other class) to one of its ancestors.

    You can write arr[i].parentNode.parentNode.parentNode.className = 'current'; to apply the ‘current’ class to the 'li' that is part of the 'ul#menu' list.

    I hope it makes sense…

  11. mark
    mark says:

    Great script! This could be just what I have been looking for, however I’m new to all things about web design and I would like to know how this script can be used via a dreamweaver template on a site with 100s of pages with large numbers of pages requiring the same highlighted link in a navigation menu. I need to be able to lock the nav menu into a non-editable region of the template. Can someone please help me?

  12. Armand Niculescu
    Armand Niculescu says:

    Mark, there’s nothing special about using the script within a DW template — actually I wrote it specifically to be used with it. For example http://www.whizz.com uses it with Dreamweaver templates.

    Have a look at the page with View Source to see how it works (I think I’ve also updated the javascript code since I wrote the article).

  13. Alice
    Alice says:

    Hello, thanks for the code, is very neat.
    I’m trying to include the java-script and instead to call the setPage() in the page, I have this added to the js:

    window.onload=function(){
    setPage();
    }

    Thanks again! (love the captcha)

  14. Alice
    Alice says:

    Also, to make it to work you need to replace
    setActiveMenu(document.getElementById(‘navbar’).getElementsByTagName(‘a’).extractPageName(hrefString));

    with:
    setActiveMenu(document.getElementById(‘navbar’).getElementsByTagName(‘a’),extractPageName(hrefString));

  15. Jeremy
    Jeremy says:

    Thanks for the script! Works great. However, I have a suggestion, related to what Ahmed G said about query strings. It should also be noted that “anchors” at the end of urls also break the script if coming from another page, or simply reloading the page w/ the anchor still attached to the url (i.e. blah.com/page.htm#end), the same as query strings do. Here’s a simple fix I wrote to address both in only 2 lines of code, plus slightly altering one of your existing lines of code:

    New code in bold
    Altered code in italics


    function extractPageName(hrefString)
    {
    var hrefString = hrefString.split('#');
    var hrefString = hrefString[0].split('?');

    var arr = hrefString[0].split('/');

    return (arr.length<2) ? hrefString : arr[arr.length-2].toLowerCase() + arr[arr.length-1].toLowerCase();
    }

    Please note, “if statements” to check for “?” and “#” are unnecessary in my code, because if they do not exist, the result of the split is simply a single item array. Much easier than doing the checks and there are no errors either. Hope that’s helpful for someone.

    [Armand: Excellent post, Jeremy. Thanks!]

  16. Meme11
    Meme11 says:

    Administrator, you’re the man!! This works like a charm! And a million “thank you’ to Ahmed G, who’s solution for query string pages work great.
    So if you have dynamicaly generated pages like this for example: – browse.php?c=1 – you have to modify the “extractPageName” function in highlight.js with Ahmed G’s mod:
    [code]
    function extractPageName(hrefString)
    {
    var arr = hrefString.split('.');
    // Check to see if the url has a query string by searching for the question mark
    var hasquestion=hrefString.indexOf('?');
    if (hasquestion == '-1')
    {
    arr = arr[arr.length-2].split('/');
    return arr[arr.length-1].toLowerCase();
    }
    else
    {
    return arr[arr.length-1].toLowerCase();
    }
    }
    [/code]
    for this to work only in current page, otherwise all pages like
    browse.php?c=# (where # is the number of the page) will be highlighted.
    Hope this helps.

  17. Lists
    Lists says:

    Great tips.
    It’s always a pain marking an active page, this looks a great solution.
    I’m going to try it right now.

    Thanks!

  18. Paul Carlisle
    Paul Carlisle says:

    This is a nice solution, but as Ben Dalzeil pointed out it suffers from a problem: it fails for pages referenced indirectly through their index.html file.

    If I invoke the page http://www.mydomain.org/topic/index.html via http://www.mydomain.org/topic/, HREF doesn’t tack on ‘index.html’; it simply passes along the URL with the slash-suffixed pathname. Unfortunately, there doesn’t seem to be any way to extract the explicit filename using JavaScript, at least not that I’m aware of.

    My not very elegant solution is to maintain a list of objects, each of which stores the title and absolute URL of the page in question. I can then compare the titles, as I can extract document.title with ease – as long as I’m careful to name them the same in both places, which is certainly a shortcoming of this approach.

    Using some sort of modified Sitemap that would be read and used to set both the stored titles and the page titles themselves would solve this problem, but suddenly this project is ballooning in size…

  19. Tom
    Tom says:

    Was attempting to do this with .NET but it was getting messy. This is a fantastic bit of code :)

    Only problem I’m having so far is:

    I’m on a page called /about/index.aspx
    So where I have “about home” – it highlights it nicely.

    However, in my navigation I have a link to /contact/index.aspx

    And it highlights that as well. So I have two highlighted items.

    Now, it’s not a major problem as I just removed the “index.aspx” out of the link to /contact.

    But just thought I’d let you all know.

  20. gap tooth clan
    gap tooth clan says:

    I think this is a great function, would have had to code it myself if not found it.

    I have just been investigating jquery looks pretty good, I am sure it could do this more efficiently.

    Could do with a load handler and an add class function rather than just changing the class to whatever.

    But good work all round

  21. Brian
    Brian says:

    As Ben Dalzeil and Paul Carlisle pointed out above, this doesn’t work on “index” pages that reside in different directories (such as /about/, or /contact/ etc.). Great code otherwise, and I’m glad to see this topic being tackled!

    Does anyone have a recommended solution?

  22. Strother
    Strother says:

    I have the same problem as Tom – 4 posts up. I use Zope and I am forced to have links with a forward slash – like

    Club/index_html
    Sailing/index_html

    and the java script doesn’t separate them – and both links are highlighted when you are on one or the other. If I could make the links

    Club.index_html
    Sailing.index_html

    It would work fine. Can you or someone suggest how the javascript should be modified to work with the forward slashes and distinguish between the links. Thanks.

  23. Armand Niculescu
    Armand Niculescu says:

    To all having issues with files having the same name in different folders: I actually fixed this an year ago, but I only updated the code on screen, not the one in the archive.

    If you download the code again, it should work.

  24. Kasun
    Kasun says:

    Thanks a lot, I was thinking weeks to do it without any satisfiable solution, Thanks again for you all.

  25. Cory
    Cory says:

    Great script! But could it be modified for image-based active states where you have different graphics files to load for each hover/stick state? For instance if I wanted it to be a different background image for each class as in the following …

    ul#menu li.home a {
    background: url(images/menu_home.png) 0 0 no-repeat;
    width: 67px;
    }
    ul#menu li.locations a {
    background: url(images/menu_locations.png) 0 0 no-repeat;
    width: 119px;
    }
    ul#menu li.about a {
    background: url(images/menu_about.png) 0 0 no-repeat;
    width: 115px;
    }
    ul#menu li.contact a {
    background: url(images/menu_contact.png) 0 0 no-repeat;
    width: 136px;
    }

    I’m assuming the .js file would have to be modified to discover which page the specific class is on, then apply that specific class using the CSS file. So instead of one class for the active state, there might be several from which to choose.

    Thanks in advance!

  26. Eugen
    Eugen says:

    Thank you for making this available for all of us.
    I’ve been looking for something like this for quite some time.

    All the best!

  27. Joshua Dyball
    Joshua Dyball says:

    This is great technique. It’s a shame about it’s one achilles heel of not showing the current page (home) when browsing the root domain (rather than index.html). Is there no easy solution for this?

  28. James
    James says:

    Thats a nice little script, I found this. My only issue with it is that is searches the whole page but it works.

    <script type="text/javascript">
    var anchors = document.getElementsByTagName("a");
    var uri = encodeURI(window.location.href).toLowerCase();
    for(var i=0, n=anchors.length; i<n; ++i) {
    if(uri==encodeURI(anchors[i].href).toLowerCase()) {
    anchors[i].style.className += " current ";
    break;
    }
    }
    </script>

    source can be found by searching google with
    “javascript dynamically highlight current page”

    first result

    Very happy, am always dealing with php generated navigation and have no control over the html. Now I kinda do!!

  29. Daniel P
    Daniel P says:

    I am struggling with hrefs having “#” as only value (a href=”#” onclick=”javascript:showEtc…).

    Thanks a lot for a beautiful solution! I have a problem though:

    When I use Armand’s original code, the page on reloading switches between highlighting links to:
    “current_site.html” (as the browser address field displays: current_site.html)
    and highlighting links to:
    “#” (as the browser address field displays: current_site.html#)

    When I use Jeremy’s revised code I get highlighted links for both “#” and “current_site.html”, no matter what is in the browser address field, no matter reloading or not.

    I don’t want any links to “#” highlighted in any condition. In my n00b javascript eyes, it looks like Jeremy’s revised code should do the trick, but it doesn’t. Anyone has an idea on what to do?

  30. Bill Dashfield
    Bill Dashfield says:

    Nice little script – just what I was looking for.

    One little mistake, I think (also what Alice was trying to say?):

    In your explanation you say ‘all links that are contained in the navigation bar (any tag with id=”navbar”)’
    but your code says:

    if (document.getElementById(“nav”)!=null) setActiveMenu(document.getElementById(“nav”).getElementsByTagName(“a”)

    To find your navigation links, I think the “nav” in the code should be “navbar”:
    if (document.getElementById(“navbar”)!=null) setActiveMenu(document.getElementById(“navbar”).getElementsByTagName(“a”)

    Thanks Bill

    • Armand Niculescu
      Armand Niculescu says:

      It’s actually a lot easier to do this in PHP but it depends on how your site is set up — there is no general answer.
      Generally speaking, you output the menu and for each item you check if it’s the current page (how? depends)
      E.g.:
      <li >Item 1
      <li >Item 2

  31. Tim
    Tim says:

    First of all…THANKS a bunch, this is exactly what I’ve been looking for.
    I’m sure I’m missing something on this. But I’m not able to figure out how to get the parent LI to highlight even if it’s a child of that li. So for example if there is any li tags that are underneath the parent to still highlight the parent and not the current li. http://www.capellnet.com/SITETEMPLATE

    Here’s the code I have currently have for the menu.

    MenuItem 1

    MenuItem 1.1
    MenuItem 1.2
    MenuItem 1.3

    MenuItem 2

    MenuItem 2.1
    MenuItem 2.2
    MenuItem 2.3
    MenuItem 2.4
    MenuItem 2.5

    MenuItem 3
    MenuItem 4
    MenuItem 5

    setPage()

    Here’s the JS info:
    arr[i].className = “current”;
    arr[i].parentNode.className = “current”;

    Here’s the CSS for the menu section:
    /***TOP NAVMENU***/
    #jsddm {
    float: left;
    width: 922px;
    height: 25px;
    background-image: url(../images/nav1.gif);
    background-repeat: repeat-x;
    margin:0;
    padding:0;
    }
    #jsddm li {
    float:left;
    list-style:none;
    }
    #jsddm li a {
    display: block;
    padding: 5px 12px;
    text-decoration: none;
    background-image: url(../images/nav1.gif);
    background-repeat: repeat-x;
    font-family: Verdana, Geneva, sans-serif;
    font-size: 12px;
    font-weight: bold;
    color: #666;
    width: 91px;
    white-space: nowrap;
    }
    #jsddm li ul {
    margin: 0;
    padding: 0;
    position: absolute;
    visibility: hidden;
    }
    #jsddm li ul li {
    float: none;
    display: inline;
    }
    #jsddm li ul li a {
    width: auto;
    border-bottom: 1px solid #CCC;
    }
    #jsddm li a:hover { background-image:url(../images/nav2.gif);background-repeat:repeat-x;}
    #jsddm li ul li a:hover{background-image:url(../images/nav2.gif);background-repeat:repeat-x;}
    #jsddm li a.current {background-image:url(../images/nav3.gif);background-repeat:repeat-x;color:#FFF;}

    Thanks for any light you can shed on this… :-)

    • Armand Niculescu
      Armand Niculescu says:

      Tim,

      if you have a nested menu structure and want to highlight parent elements as well as children, eg:

      
      <ul>
      <li><a href="#">Item 1</a></li>
      <li><a href="#">Item 2</a>
      <ul>
      <li><a href="#">Item 2.1</a></li>
      <li><a href="#">Item 2.2</a></li>
      <li><a href="#">Item 2.3</a></li>
      </ul>
      </li>
      <li><a href="#">Item 3</a></li>
      </ul>

      then, at line 16, you need to add

      
      arr[i].parentNode.parentNode.className = "current";
      arr[i].parentNode.parentNode.parentNode.className = "current";
      

      arr[i] is the link itself (….)
      arr[i].parentNode is the LI tag that contains the link
      arr[i].parentNode.parentNode is the submenu (UL tag)
      arr[i].parentNode.parentNode.parentNode is the parent element (LI)

      I hope it’s not too confusing….

      If you are serious about web development, I recommend you use the Firebug plugin for Firefox. It will show you exactly how properties are applied.

  32. Juan
    Juan says:

    With regards to the question of whether I can highlight two different links on the same page, for example, if I have a main navigation call “about us” and a left navigation call “vision and mission”, how do I get both “about us” and “vision and mission” highlighted. Take it that “aboutus.html” and “visionandmission.html” are in the same folder? Thanks. :)

  33. Rob Macintosh
    Rob Macintosh says:

    Fantastic – just what i’ve been looking for, I *think*

    Could this be modified to display a different image instead of simply highlighting in CSS.

    For instance if you were to set the background of a particular class as an image, that would work right?

    I’m trying to keep my menu navigation, which is 5 image buttons, in a single header.php. This header.php is included on each of my pages – but obviously as you navigate to each of the 5 different pages, I want the image button for that page to be highlighted (with another image).

  34. Judith Pattinson
    Judith Pattinson says:

    This looks like what I’ve been desperately searching for, particularly because the highlighting survives screen refresh.

    Unfortunately I have to have my menu in a separate frame. I’m wondering whether it would be possible to compare the link in the menu to an href that opens in another frame.

    Apologies if this is a silly question. Any advice appreciated.

    • Armand Niculescu
      Armand Niculescu says:

      I assume you want the menu item to be active, as well as its parent, right?
      It’s a bit difficult to make a generic solution for multiple levels as it depends on the html. If you need something specific, contact me privately.

  35. Nick Wilcox
    Nick Wilcox says:

    works awesome – with a little custom tailoring, it did exactly what I needed.

    kudos and thanks!

  36. Fergus O'Donoghue
    Fergus O'Donoghue says:

    Nice little script. Needs a couple of mods referring to how to get the parent LI to highlight even if it’s a child of that li. …I’m working on it and will post in due course.

  37. aixxa
    aixxa says:

    I would like to know how to turn this page into cakePHP.
    I mean how to apply this code in cakePHP frame work. i’ m new in cakePHP.
    help please..

Comments are closed.