Dynamic category menu highlighting for single posts

A tutorial to show how to implement dynamic menu highlighting of Wordpress category menus when viewing single post pages.

Cyberspace is a place

Gosh! That’s a mouthful, isn’t it, but what does it mean? Quite simple, really. In this tutorial we’re talking about how to dynamically highlight the relevant category menu item when viewing a single post which belongs to that category. Look at my menu bars and you should see that Code Snippets is highlighted – which is pretty cool given that you are browsing a single post.

The following assumes that (a) your theme has a category navigation menu bar or similar, and (b) this menu is generated by the Wordpress wp_list_categories() function.

Before we begin I have to confess that I didn’t come up with this code all by myself. The code that we will use is derived from this www.designshard.com article, but has been tweaked by me to make it even more useful for day-to-day use. The problem with the method outlined in that article, and the Show Category Archive plugin that is referenced therein, is that those methods add a CSS class to the menu <a> tag, whereas the commonly used menu function wp_list_categories adds dynamic “menu state” classes to the links’ li tag. It seems much more useful to try to fit in with what Wordpress is already doing, rather than create something outside that structure.

Anatomy of a wp_list_categories menu

By all means, be my guest and dig into the wp_list_categories source code to see exactly how wp_list_categories works. But let’s not get too technical, and look at a simple example instead. Here’s the example menu code from a typical theme’s header.php template file:

<div id="subnavbar">
	<ul id="subnav">
		<?php wp_list_categories('orderby=name&title_li=&depth=4'); ?>
	</ul>
</div>

If we view the Page Source of a page other than a Category archive page, this is the markup that this code generates:

<div id="subnavbar">
	<ul id="subnav">
		<li class="cat-item cat-item-1"><a href="url to fruit category page" title="View all posts filed under Fruit">Fruit</a></li>
		<li class="cat-item cat-item-2"><a href="url to veg category page" title="veg category desc">Vegetables</a></li>
		<li class="cat-item cat-item-3"><a href="url to dogs category page" title="dogs category desc">Dogs</a></li>
		<li class="cat-item cat-item-4"><a href="url to penguin category page" title="penguin category desc">Penguins</a></li>
	</ul>
</div>

Putting aside the diverse range of topics handled by this category menu, you will notice that the function automatically outputs 4 pieces of code for each category:

  • cat-item – a CSS class added to the li tag of every category in the menu.
  • cat-item-x – a CSS class added to each li tag, where X is the category ID number. Very useful if you want to style individual menu items differently.
  • A link to the relevant category archive page (not shown in the above example, in order to keep things simple).
  • A title attribute for each link. Notice that this is pulled from the Category Description, assuming one exists, and if it doesn’t, automatically adds “View all posts filed under (category name)” to the link’s title attribute.

If we were to click on the Fruit category link and look at the Page Source of this page, this is what we will see (simplified for clarity):

<div id="subnavbar">
	<ul id="subnav">
		<li class="cat-item cat-item-1 current-cat">...link stuff...</li>
		<li class="cat-item cat-item-2">...link stuff...</li>
		<li class="cat-item cat-item-3">...link stuff...</li>
		<li class="cat-item cat-item-4">...link stuff...</li>
	</ul>
</div>

As you can see, wp_list_categories has added a CSS class of current-cat to the li tag of the Fruit category. By adding a suitable CSS style to the stylesheet to target current-cat, it’s easy to highlight the menu item when viewing any category archive page.

However, as soon as you click on a Post title, this highlighting is lost. This is because wp_list_categories relies on the category query variable, sent to the server when a category link is clicked, in order to know which category li tag to add the current-cat to. When viewing a single post, this query variable is not available to wp_list_categories, therefore the current-cat disappears from the generated markup.

The challenge

In order to get around this problem, we need to find a way to preserve the current-cat class in the relevant category. Happily, thanks to Wordpress filters, this is easy to do (yes, there is a wp_list_categories filter), so all we need do now is come up with a suitable function for this filter, which needs to do the following:

  • Find out which post is being requested
  • Determine which categories are assigned to this post
  • Add current-cat to the li tags of these categories (as posts could be assigned to more than one category)
  • Return the result of the above back to Wordpress so that it can continue generating the page to be viewed.

The function

Here it is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function sgr_show_current_cat_on_single($output) {
 
	global $post;
 
	if( is_single() ) {
 
		$categories = wp_get_post_categories($post->ID);
 
		foreach( $categories as $catid ) {
			$cat = get_category($catid);
			// Find cat-item-ID in the string
			if(preg_match('#cat-item-' . $cat->cat_ID . '#', $output)) {
				$output = str_replace('cat-item-'.$cat->cat_ID, 'cat-item-'.$cat->cat_ID . ' current-cat', $output);
			}
		}
 
	}
	return $output;
}

Let’s take a closer look…

  • Line 1: Remember – Filters are a way of adding additional processing into the normal data processing flow of Wordpress. Therefore, there will be something coming in to our filter function (the $output argument), which is then processed by our function, and then is returned back to Wordpress when we’re finished (see line 18). In this case, $output will be the normal output of wp_list_categories being, essentially, the markup shown in the first block of code above.
  • Line 3: We need access to the $post variable so that the function knows which post is being called.
  • Line 5: This check using Conditional Tags ensures that our function only does something when a single post is being called. That’s something to remember with Filters – only process what actually needs to be processed. On every other type of page, such as when generating a category archive page, we don’t want our filter function to affect the normal output of wp_list_categories, hence this check.
  • Line 7: Now that we have access to the $post variable, we can use the wp_get_post_categories() function to generate an object containing database details of all categories assigned to the post.
  • Line 9: We now use a foreach loop to loop through each category found.
  • Line 10: Using the get_category() function we get access to the data specific to the category being looped through. Specifically, we want to get hold of the category ID number.
  • Line 12: We now use a PHP function preg_match() to search for “cat-item-1″ (for example) within the li tags contained in $output (the markup generated by wp_list_categories and which we’re processing).
  • Line 13: If line 12 finds a match, we then use another PHP function, str_replace(), to replace “cat-item-1″ with “cat-item 1 current-cat”. Hey Presto! We’ve now done the important bit – added current-cat to the li tags belonging to the categories the post has been assigned to.
  • Line 18: Finally, we pass our modified $output back to the wp_list_categories function, for final processing by Wordpress and the rendering of the page markup.

Add this function to your theme’s functions.php file.

Adding the Filter

Now that we have our function, we need to tell Wordpress to use it by adding the necessary Filter, again in functions.php:

add_filter('wp_list_categories', 'sgr_show_current_cat_on_single');

Adding CSS styles

The final step is to add a suitable CSS style to the theme’s style.css. Each theme will be different, so you may need to experiment with different selectors in order to find the right one.

Taking our example markup as a starting point, something like this should work:

#subnav li.current-cat a { ...put your styles here...}

As mentioned, depending on the structure of your theme’s markup, you may need to experiment a little to get the selector right. The Firebug add-on for Firefox is an excellent tool to help you do this.

Filed under Code snippets

Comments

27 Responses to “Dynamic category menu highlighting for single posts”

  1. BrentU says:

    Great tutorial!

    Do you know if this will work for pages, as well? I’m currently building my WP site using page templates, rather than category templates for displaying category posts (I thought it would create cleaner web-friendly URLs). Do you know if I could simply exchange ‘categories’ with ‘pages?’

    Thanks

    • Ade says:

      Glad it was useful. :-)

      Yes, in principle, though the markup output by wp_list_pages() is different to that described for categories.

      If I have time, I may do a follow up article using pages.

      • BrentU says:

        Ade,

        Sorry to populate your comments with so many questions, but you really seem to know your stuff.

        I’ve abandoned using wp_list_categories for navigation and I will now be using wp_list_pages (for numerous reasons). I would really like to dynamically highlight my page menu when on single posts. I’ve found that many other people are also looking for a solution. Any chance you could do a follow up article on this . . . soon? ;-)

        Thanks,
        Brent

  2. BrentU says:

    Another question for you . . .

    Do you know if it is better to use Pages Templates or Category Templates to display info for search engines? The category template adds “/category/” to the URL before a category title. I would love to hear your thoughts ;-)

    • Ade says:

      Frankly, I don’t know. I’m sure there are a million sites out there which will express a variety of views. I like to think that, primarily, Search engines want human users to be able to get relevant search results in order to find what they’re looking for.

      I guess the answer depends on the content of the site – is navigation and usability more logical and more easily obtained with posts/categories rather than pages? The answer will vary from site to site.

      • BrentU says:

        The site I’m building is a creative services directory which uses many levels of categories to help narrow searches. I’ve done a bit of research on this page template vs. category template question, but haven’t found many answers. Maybe this would be a good article/post topic?

        Thanks for your insight!

        • Ade says:

          My view is that if you have regularly changing content, combined with a need for a taxonomy whose functionality can be provided by Categories, go for it. I’m a firm believer in “content is king”, and that structured content, good keyword density, etc is the best way to good SE rankings. Pages have certain limitations (though this may improve in future versions of WP) and are not, in my opinion, suited to constantly added content and complicated hierarchies.

          Agreed – could be an interesting topic for a future post. :-)

  3. BrentU says:

    Ade,

    This solution works beautifully! Thanks!

    On a side note: do you have any idea how to achieve dynamic category menu highlighting for 3rd level(or grandchild) categories? Wordpress has yet to develop a “.current-cat-ancestor” css class for categories (like they have for pages: “.current_page_ancestor”).

    I’ve been pulling my hair out trying to find a solution.

    • Ade says:

      Brent,

      It does work – but there are a couple of points to remember.

      1. If you’re using dropdowns, the grandchild category name will only be visible on mouseover, hence you won’t see any highlighting when the menu is closed.

      2. If you want the highlighting to appear when the dropdown is open (if you see what I mean), you’ll need to add suitable styles in the CSS.

      For example, let’s say you have a style for highlighting your top level categories, like this:

      #subnav li.current-cat a {
      	color: #000000;
      	background: url(images/excerpt_flash_small.png) no-repeat 0 0;
      	}

      You would also need to target the next level of li tags, like this:

      #subnav li li.current-cat a {
      	color: #000000;
      	background: url(images/excerpt_flash_small.png) no-repeat 0 0;
      	}

      Assuming you’re using pseudo-classes, eg a:hover, a:link, a:visited, a:active, you will have to modify the above to target these too.

      Another point to consider is whether you assign a post to all categories in the hierachy, eg to the parent, child and grandchild cats. If you do, the highlighting will appear on all three cat tabs – which is probably confusing to the site visitor. Only ever assigning posts to one category (in any one hierarchy) is probably a better solution IMO.

      • BrentU says:

        Ade,

        Thank you for your detailed reply. I still can’t get my parent link to stay highlighted once clicking down to the 3rd child link. I tried adding a:active, li li, etc., but it just isn’t working. May be it isn’t working because the links are in two separate navigation menus (main nav and sidebar)?

        Anyway. Thanks for trying to help out on this.

        Cheers,
        Brent

        • Ade says:

          Hi Brent,

          Yes, I assumed you were talking about only one instance of wp_list_categories(), ie one menu bar. You would need to explain how your two menu bars work.

  4. Razz says:

    Hello!

    I would like to thank you for useful post :-)

    I’m wondering if there is a way to highlight parent category if there subcategories too, but when i select just subcategory as category base for post..

    Hope you understand me :D

  5. MFSB says:

    Really clear explanations indeed, makin’ the things much easier as compared to a whole bunch of infos displayed here or there…
    Currently working on a site under development and worked liked a charm.
    Warmful thanks & Gruezi,
    Frederic

  6. Razz says:

    So is there any chance to highlight “empty” parent category with a post assigned to a child category?

    Thanks,
    RAzz

    • Ade says:

      Honestly, I haven’t tried it. Bit tied up at the moment but I will revisit this at some point.

      • Razz says:

        Thanks for answer Ade.. If I find solution for this, i ll post it here..

        Regards

        • MFSB says:

          Also tried to do this with no success so far, as I don’t want to have my homepage invaded by a dropdown menu. Made a couple of tests from Ade’s code trying the sub-cat tag but it doesn’t look like it’s accepted.
          The only thing I could think of so far is using some conditional statement like is is cat something or somewhat, although I have not tested this yet.
          Peace everybody,
          Frederic

        • Ade says:

          The method I used in this article won’t work with an “empty” category. This is because the empty cat ID will not be found by this:

          $categories = wp_get_post_categories($post->ID);

          There may be another way to do by detecting category parents (as hinted at by Frederic in his comment), but I haven’t tried it personally.

        • Alan Holmes says:

          I believe I may have come up with a solution to this. As with others, I have parent (empty) categories that needed highlighting too. I’ve still got some testing to do on it, and ill be doing an article on my website (crediting this article as its base), if it is successful, I’ll provide the link once I do.

  7. Alan Holmes says:

    Well the last comment didn’t seem to submit correctly (though I apologize in advance if it did). Anyway, all my tests seem to have worked, and I’m looking forward to seeing if it does for everyone else.

    The Article can be found here

Leave a comment

Interesting article? Or a load of rubbish? Let me know...
and oh, if you want a pic to show with your comment, go get a gravatar!

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">



Trackbacks

See what others are saying about this post...