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 the co-founder and lead developer at CultivateWP, a WordPress agency focusing on high performance sites for web publishers.

About Me
Ready to upgrade your website?

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

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson

Comments

  1. 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 …]

  2. 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.

  3. 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 😉

  4. 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

  5. Lee says

    Hello Bill,
    Thank you for the code, it was great guidance to add the class to the menu items.

    It works, but a small caveat to it.
    I have 3 of my top bar menu items as a dynamic menu– scrolls to the proper section.

    The other 3 menu items are pages that will direct user to proper page.

    The items in the dynamic/scrolling to section links, when the .current-menu-item applies to menu item clicked, it adds to all three dynamic menu items.

    Is there a way to just highlight the one that was clicked? Or it just doesn’t work that way?
    I’m trying to find if could set a conditional to check if the article id matches…but I really don’t think that will matter much as it seems the ‘default’ of the dynamic menu items is they are all ‘one’?

    Any thoughts on this small glitch?

    Thank you

    • Bill Erickson says

      Unfortunately there’s no way to do that on the WordPress/PHP side because that information is not available, but you could do it with JavaScript.

  6. Brian says

    Bill, I’m having a hard time getting mine to work correctly. I have two categories listed in my primary menu. One menu item is “Arizona Wine” and another is “Arizona Beer”. When I edit this line:

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

    And click on a wine post, the current-menu-item acts as it should by showing “Arizona Wine” as current. But then when I click an “Arizona Beer” post, Arizona Wine will show current instead of showing Arizona Beer as current.

    Can you help me separate the two?

    • Bill Erickson says

      The code snippet you provided says “if this is a post, a category, or a tag, mark “Arizona Wine” as the current menu item”.

      I think what you want is `if ( ( is_singular( ‘post’ ) && in_category( ‘arizona-wine’ ) ) && ‘Arizona Wine’ == $item->title )`

  7. Brian Hughes says

    Dang, Bill… that worked! You are quite the genius at this stuff. I’ve thumbed through all of your snippets and I think I can speak for us all when I say I sure do appreciate the time and effort you’ve spent sharing your work with us. Oh, and thanks for your quick response!

  8. Brian Hughes says

    In an effort to better organize my content, I’ve decided to use custom post types. Is there any way to implement this for custom post types?

  9. Cliff says

    I discovered that if I have an anchor link in my menu along with the non-anchor link item in the menu, BOTH get marked as the .current-menu-item

    🙁

    In a way, it makes sense because the Post ID is the same (assuming that’s how it’s identifying from native WP, no custom code)

    Any override for that quick at your fingertips?

    • Bill Erickson says

      You could use the filter shown in the article above to check if the menu item has a class of ‘current-menu-item’ and contains an “#” in the URL (ex: false !== strpos( $item->url, '#' )), then remove the ‘current-menu-item’ class.