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

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.

enter image description here

/**
 * 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.

In action