How can I add a class to a nav li depending on URL?

The question:

I’m trying to add a .current-menu-item class to a li when on a specific url “/portfolio/”. I can achieve this with jQuery:

jQuery(document).ready(function( $ ) {
  var loc = window.location.href; // returns the full URL
  if(/portfolio/.test(loc)) {
    $('#menu-item-33').addClass('current-menu-item');
  }
});

However would like to do with PHP. This what I have so far:

function add_class_to_specific_menu_items( $atts, $item, $args ) {
    // check if the item is set to target="_blank"
    if ( $item->url == '/portfolio/' ) {
      // add the desired attributes:
      $atts['class'] = 'current-menu-item';
    }
    return $atts;
}
add_filter( 'nav_menu_link_attributes', 'add_class_to_specific_menu_items', 10, 3 );

Not sure if this is viable and/or if I can have it add the class to a specific li “#menu-item-33

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

Here is the solution.

To set it up:

Paste the following at the bottom of the file functions.php in the folder of your current theme.

Edit by the 🟢 in the code`

$when_on_one_of_these_page_slugs should be an array of strings. The strings are the slugs of the pages on where you want the rule to kick in. The script will kick in the rule if you are on:

  • One of these slugs.
  • A child of any of these slugs.

Edit by the 🟣 in the code:

$change_these_nav_menu_items should be a list of menu item id’s, of the item you want to look selected, when the rule kicks in.

You can find the ID of the menu in dev tools by right clicking on the menu item and choose “inspect element”. You will see the source code of the generated page, and you will find the item ID in the <li> tag wrapping the <a> of the item you inspected.

How can I add a class to a nav li depending on URL?

/**
 * Makes specific menu item look active when visiting a certain page
 *
 * @param array $wp_nav_meny Array of items from a WP Nav Menu.
 *
 * @return array modified array of WP Nave Menu items.
 * @see https://wordpress.stackexchange.com/questions/389050/how-can-i-add-a-class-to-a-nav-li-depending-on-url
 */
function make_x_active_when_on_y( array $wp_nav_meny ) {


    // 🟢 Set an array of page slug (strings) to trigger the rule.
    $when_on_one_of_these_page_slugs = array( 'portfolio', 'add-any-slug-that-should-trigger-the-rule');
    // 🟣 Set an array menu item id's (ints) to apply the rule to.
    $change_these_nav_menu_items = array( 33, 999);

    // Get the ID's of the pages we added in the setting.
    $post_ids_to_trigger_rule = wp_list_pluck(array_map('get_page_by_path',  $when_on_one_of_these_page_slugs ), 'ID');
    // Get all the ancestors of the page we are curently visiting.
    $ancestors_of_current_page = get_post_ancestors(get_queried_object_id());
    // Add the current page into the array of ancestors.
    array_push($ancestors_of_current_page, get_the_ID());

    $new_menu = array();

    // Loop through the nav menu items.
    foreach ( $wp_nav_meny as $menu_item ) {
        /*
         * If the ID of the current page, or any of it's ancestors,
         * exists in the array of ID's that should trigger the rule,
         * AND
         * The current item ID in the loop, is in the array if nav menu
         * item id's that sould get the "current-menu-item" class.
         */
        if ( array_intersect($post_ids_to_trigger_rule, $ancestors_of_current_page) && in_array( (int) $menu_item->ID, $change_these_nav_menu_items, true ) ) {
            $menu_item->classes[] = 'current-menu-item';
        }
        $new_menu[] = $menu_item;
    }
    return $new_menu;
}
add_filter( 'wp_nav_menu_objects', 'make_x_active_when_on_y' );

How it works

When you visit a page that has the slug you set at the 🟢, or a page that has that page as an ancestor, then the rule kick in.

The rule is that the menu item that you set at 🟣 will have the class current-menu-item.

Below you can see a recording of the result.

This is a completely fresh installation of WordPress 5.7.2, running PHP 7.4.1. No plugins installed. The theme is twentytwentyone (the ones that is shipped with WordPress).

My config looked like this:

$when_on_one_of_these_page_slugs = array( 'portfolio'); // The slug of my portfolio page.
$change_these_nav_menu_items = array( 24 ); // The item ID of my "A link to Google"-menu item.

How can I add a class to a nav li depending on URL?

Method 2

Try the following tested solutions they are working 100% on the frontend screen because both solutions are based on the wp filter hook wp_get_nav_menu_items which is executed where the wp_get_nav_menu_items() function is called.

Solution 01: Add CSS class by comparing menu item ID

function add_class_to_specific_menu_items( $items, $menu, $args ){
    foreach ( $items as $index => $menu_item ){
        // conditional statement to compare the menu-item id
        if( $menu_item->ID == 33 ){
              // conditional statement to check the current url is same
              if( get_permalink() == $menu_item->url ){
                  $menu_item->classes[] = 'current-menu-item';
              }     
        }
    }
  return $items;
}
add_filter('wp_get_nav_menu_items', 'add_class_to_specific_menu_items', 10, 3);

Solution 02: Add CSS class by comparing menu item URL

function add_class_to_specific_menu_items( $items, $menu, $args ){
    foreach ( $items as $index => $menu_item ){
        // conditional statement to compare the menu-item URL
        if( $menu_item->url ==  get_site_url().'/portfolio/' ){
               $menu_item->classes[] = 'current-menu-item';
        }
    }
  return $items;
}
add_filter('wp_get_nav_menu_items', 'add_class_to_specific_menu_items', 10, 3);

Method 3

@modyjive — the answer @ejaz-ul-haq gave is congruent with your PHP code given. Your PHP code tries to add the current-menu-item class to anchors that contain the /portfolio/ URL in their HREF attribute. Your code isn’t quite right, so @ejaz-ul-haq fixed it up so that it gives the represented effect. But, I’m interpreting your question to mean something different: you want to convert the functionality of the JavaScript provided into something WordPress-able. If that’s what you want, here’s my answer.

We first determine what your JS does:

jQuery(document).ready(function( $ ) {                    // When the page loads,
    var loc = window.location.href;                       // save the current URL
    if(/portfolio/.test(loc)) {                           // and check if it contains '/portfolio/'.
        $('#menu-item-33').addClass('current-menu-item'); // If so, add `current-menu-item` to the class list of all elements with id="menu-item-33"
    }
});

Immediately, we run into a problem: your JS will add current-menu-item to any and all HTML elements having an id="menu-item-33" attribute. So, it will happen in the backend as well as the frontend and in some places you might not even know exist. I’m going to go out on a limb here, but I’m guessing you’re using the nav_menu_link_attributes hook because you’re trying to manipulate navigation menus printed on the front-end, maybe? If so, let’s dig into the WordPress documentation to see how those are printed out. (I’ve provided links in the text below so you can follow along; if you’re not familiar with tracing WordPress code, now’s your chance to learn.)

First, we figure out a starting point. You used the nav_menu_link_attributes hook, so let’s start there. From the reference guide, it is learned this hook is found in the /wp-includes/class-walker-nav-menu.php file. According to the header notes of the hook, nav_menu_link_attributes “Filters the HTML attributes applied to a menu item’s anchor element.” The anchor element (<a>) is located inside the list item element (<li>). Your JavaScript represents that you want to add a class to the list item element, not the anchor. If we scroll up to line 170, we can see where the list item is added to the output stream. That line looks like this:

$output .= $indent . '<li' . $id . $class_names . '>';

We can see on lines 167 and 168 where $id is generated and on lines 153 and 154 where $class_names is generated. By hooking into the nav_menu_css_class filter (on line 153), we can miniplate the $class_names variable before it is output in the <li> opening tag on line 170, which I believe is what you want! According to the WordPress reference, the nav_menu_css_class hook “Filters the CSS classes applied to a menu item’s list item element.” Yup, that’s what your JS does, so we’re on the right track.

So, here we go:

function add_class_to_specific_menu_items($classes, $item, $args, $depth){
    global $wp;
    $loc = add_query_arg($wp->query_vars, home_url($wp->request)); // Save the current URL (like you did in your JS)
    if(stripos($loc, '/portfolio/') !== NULL                       // and check if it contains `/portfolio/`  (like you did in your JS).
    && $item->ID === 33                                            // Also check that this is menu item #33  (like what you did in your JS, but limit only to navigation menus)
    ) {
        $classes[] = 'current-menu-item';                          // and if so, add the desired class to the list item (like you did in your JS).
    }
    // No matter what, return the $classes array or we'll break things when navigation menus are output.
    return $classes;
}
add_filter('nav_menu_css_class', 'add_class_to_specific_menu_items', 10, 4);

Time for a beer (and hopefully you’ll click the answered checkmark, upvote, and give me your bounty).


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment