Customize which menu item is marked active

WordPress adds a class of .current-menu-item to menu items when you are on that page. But sometimes you might want a menu item marked active in other instances.

On my website, I wanted the “Blog” menu item to be marked active any time you’re in the blog (blog homepage, category archive, single post…). I wanted “Code” marked active any time you’re in the code section (Code Snippets listing, Code Tag, Single Code Snippet). And I wanted “Case Studies” marked active when viewing the case study section (Case Study Listing and Single Case Study).

Luckily WordPress provides a filter for that: nav_menu_css_class

The filter includes four parameters:

  • $classes (array) – the current CSS classes on the menu item
  • $item (object) – the current menu item object
  • $args (array) – the arguments for the current menu (what was passed to wp_nav_menu())
  • $depth (integer) – the menu item’s depth in the menu

For this we will only use the first three parameters. I only want my customizations to apply to my menu in the ‘header’ theme location, so I check that first. Then I see if we’re on a page I want marked active (ex: is_singular( 'post' )) and if I have the right menu item selected (ex: 'Blog' == $item->title). If all of those conditions are met, I add a class of .current-menu-item to the menu item.

<?php
/*
* Customize Menu Item Classes
* @author Bill Erickson
* @link http://www.billerickson.net/customize-which-menu-item-is-marked-active/
*
* @param array $classes, current menu classes
* @param object $item, current menu item
* @param object $args, menu arguments
* @return array $classes
*/
function be_menu_item_classes( $classes, $item, $args ) {
if( 'header' !== $args->theme_location )
return $classes;
if( ( is_singular( 'post' ) || is_category() || is_tag() ) && 'Blog' == $item->title )
$classes[] = 'current-menu-item';
if( ( is_singular( 'code' ) || is_tax( 'code-tag' ) ) && 'Code' == $item->title )
$classes[] = 'current-menu-item';
if( is_singular( 'projects' ) && 'Case Studies' == $item->title )
$classes[] = 'current-menu-item';
return array_unique( $classes );
}
add_filter( 'nav_menu_css_class', 'be_menu_item_classes', 10, 3 );
view raw functions.php hosted with ❤ by GitHub

I’ve placed this code in my theme’s functions.php file. Normally I recommend using a Core Functionality plugin for code snippets, but this one relates directly to the theme. On my next theme I might not have a ‘header’ theme location for a menu, or I might not want these menu items marked active.

Bill Erickson

Bill Erickson is a freelance WordPress developer and a contributing developer to the Genesis framework. For the past 14 years he has worked with attorneys, publishers, corporations, and non-profits, building custom websites tailored to their needs and goals.

Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments

  1. Sharon says

    Hello Bill!

    Thanks so much for this great customization tip!

    Would this also apply to pages? For instance, I would like to have 3 sub-pages of a main menu item for the mobile version of my website (in-the-works). I do not want these sub-pages included in the main navigation. Instead, I would like the primary menu page to be highlighted, when a visitor lands on each of these 3 sub-pages.

    Would your snippet work for something like this? Do you have any clues as to how I could apply it to a custom mobile menu?

    Thanks again!
    Sharon

  2. Maria Campbell says

    Thanks so much Bill for all your posts. They are so awesome, and I learn a lot! It makes me love Genesis even more.

  3. David says

    Hi Bill,
    thank you. i just use it for a site. very smart. i previously manually add custom class in menu item and style it conditionally in CSS (compared to body class).

    just a small improvement:
    when we set current-menu-class in a complex conditional, sometimes we can get double class(es). we can remove duplicate classes using array_unique:

    return array_unique( $classes );

    of course, duplicate css class still works, but it is much cleaner without duplicate 🙂
    i also use array_unique when filtering body class, post class, etc.

    thanks again for the tips.

    • Bill Erickson says

      Good call! While not necessary on body_class and post_class since they already run through array_unique after the filter (here and here), the nav menu css classes don’t go through array_unique (here).

      I’ve updated my code above to reflect this suggestion.

      • David says

        I didn’t know that wp updated body class and post class with array_unique.

        I just check 3.x and array unique is not available in core.
        probably sometime in wp 4.x

        I hope core team will the also add that in all dynamic classes.

        thanks for the information.
        I’ll put it in my check list to remove array unique in body class & post class filter.

  4. Ivan says

    Hi Bill.
    Thanks for the snippet. I was wandering how to do this for a while.

    Is there any reason you are not using if()…. elseif() statement rather than going thru each if? the code don’t look like it can get in more than one if statement. That won’t be noticeable speed improvement … but will optimize the code.

  5. Melissa Jean Clark says

    This is great! Thanks so much for sharing your code. I’m not as well-versed in filters with WP – I was using a bit of jQuery to accomplish this. This approach is much better!

  6. Aaron Jerad says

    Thanks this worked great. I was using jQuery, but this is much better!

    In my case it was likely that menu item titles were going to change. So I used the menu item ID instead, like this:

    if( ( is_singular( ‘post’ ) || is_category() || is_tag() ) && 1648 == $item->ID )
    $classes[] = ‘current-menu-item’;

    I also needed to add a class to the current menu item’s parent or ancestor so simply duplicated the conditional like this:

    if( ( is_singular( ‘post’ ) || is_category() || is_tag() || is_singular( ‘custom-post-type ) || is_tax( ‘custom-taxonomy’ ) && 1360 == $item->ID )
    $classes[] = ‘current-page-ancestor’;

    • Sonam says

      Hi Aaron –

      I’m trying to get this working with genesis and woo-commerce, so on all the sub categories for shop , the shop menu item stays highlighted as current – but no luck so far. Any ideas how to do this?

      Thanks

  7. Jim says

    Hi Bill

    Thanks for sharing. Just trying to work this out.

    What is the 10,3 at the end of your code?

    I’m trying to figure out how to show “menu-item-74” as selected if is_front_page()

    • Bill Erickson says

      10 is the priority of my filter, and 3 is the number of arguments being passed to my function. More information here.

      If you do print_r( $item ); you can see all the available parameters you can use. I think there’s something like $item->ID which will have the menu item ID, so you can do if( is_front_page() && $item->ID == 74 )

  8. Adam says

    Hi Bill! Thanks for this – REALLY giving it my best shot but just not getting the extra class added. Wondering if something here is amiss and hoping you can have a quick glance…? –Adam

    IN FUNCTIONS.PHP:

    function be_menu_item_classes( $classes, $item, $args ) {

    if( ‘main-nav’ !== $args->theme_location )
    return $classes;

    if( ( is_singular( ‘products’ ) ) && ‘Products’ == $item->title )
    $classes[] = ‘current-menu-item’;

    return array_unique( $classes );
    }
    add_filter( ‘nav_menu_css_class’, ‘be_menu_item_classes’, 10, 3 );

    THE CODE GENERATING THE NAV I WANT TO TARGET

    ‘main-nav’ => __( ‘The Main Menu’, ‘bonestheme’ ), // main nav in header

    THE MENU CODE (excerpt of particular menu item in question), which isn’t receiving the additional class:

    Products

    [… li’s …]

  9. Isaac says

    This is Great Bill!

    I have one question, how would this work for widgetized menus? They don’t seem to have a theme location of course. Is there another argument to check on for this, or will it just plain not work? Somehow the wigetized nav menus function like theme location menus though, so I assume there is a hook somewhere…

    • Bill Erickson says

      Widget areas do not have a theme location defined, but you could target your code using some other attribute of the menu, like it’s name or ID. Look at the $args parameter to see what you have to work with.

  10. Rob says

    Hey Bill, when upgrading to php 7.2 it can hit some issues with “Too few arguments to function. 2 passed and exactly 3 expected” errors.

    A quick workaround (whilst waiting for the proper code) is to adjust the first line from

    be_menu_item_classes( $classes, $item, $args)

    to

    function be_menu_item_classes( $classes = null, $item = null, $args = null )

    of course this can cause other issues downstream but it’s a quick and dirty fix if anyone’s host just bumps them straight up to 7.2 without testing 😉

  11. Jesper says

    A very nice way to do it.

    However, what if we wanted to set a specific menu item to current, if none of the conditions are true?
    With this we cannot use else or elseif(‘All the rest’ == $item->title), since they always will return true.

    To solve this I’ve used the ‘wp_nav_menu_objects’ filter instead.

    Let’s say we had a site with 3 different sections:
    – One for regular users
    – One for business users
    – One for the press

    The last two are custom post-types and should only be set to current in the menu, if you’re on a post with that specific post-type. If you’re on any other post-type, taxonomy etc. the first menu item should be set to current.

    Here’s how I did it:

    add_filter( ‘wp_nav_menu_objects’, function($sorted_menu_items, $args) {

    if ( ‘top_navigation’ !== $args->theme_location ) {
    return $sorted_menu_items;
    }

    if( in_array(get_post_type(), [‘business’]) ) {
    $sorted_menu_items[2]->current = ‘true’;
    $sorted_menu_items[2]->classes[] = ‘active’;
    }

    elseif( in_array(get_post_type(), [‘press’]) ) {
    $sorted_menu_items[3]->current = ‘true’;
    $sorted_menu_items[3]->classes[] = ‘active’;
    }

    else {
    $sorted_menu_items[1]->current = ‘true’;
    $sorted_menu_items[1]->classes[] = ‘active’;
    }
    return $sorted_menu_items;
    } , 10, 2);

    If you want anymore post-types to the specific section, just put the post-type-slug in the array

Leave A Reply