On single-page or landing-page WordPress sites where the navigation uses anchor links (#section), WordPress marks all matching anchor items as active simultaneously. This happens because the CMS compares the full current URL against each menu item URL and considers a match whenever the page URL appears in the item’s href — regardless of the fragment.
Problem: On a one-page WordPress site with anchor links in the navigation menu, WordPress adds the current-menu-item class to the wrong item — typically the Home page — instead of the currently visible section.
Solution: Remove the incorrect server-side active classes with a nav_menu_css_class filter, then use a lightweight IntersectionObserver script to add and remove an active class on the corresponding menu link as each section scrolls into view.
The jQuery approach fixes the symptom on the front end but causes a brief flicker: WordPress adds the active classes server-side, the page renders, and then JavaScript removes them. For a cleaner solution, strip the classes in PHP before they reach the browser.
Option 1 — filter without a custom Walker. Use the nav_menu_css_class filter to remove active classes from any item whose URL contains a #:
<?php
add_filter( 'nav_menu_css_class', 'remove_active_class_from_anchor_items', 10, 2 );
function remove_active_class_from_anchor_items( $classes, $item ) {
if ( strpos( esc_attr( $item->url ), '#' ) !== false ) {
$classes = array_diff( $classes, [ 'current-menu-item', 'current-menu-ancestor' ] );
}
return $classes;
}
Option 2 — inside a custom Walker. If you already have a Walker_Nav_Menu subclass, add a single line inside start_el() after $class_names is built:
<?php
class Custom_Walker_Nav_Menu extends Walker_Nav_Menu {
public function start_el( &$output, $item, $depth = 0, $args = [], $id = 0 ) {
// … standard Walker code …
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) );
$class_names = ' class="' . esc_attr( $class_names ) . '"';
// Remove active classes from anchor links — the leading space is intentional.
if ( strpos( esc_attr( $item->url ), '#' ) !== false ) {
$class_names = str_replace(
[ ' current-menu-item', ' current-menu-ancestor' ],
'',
$class_names
);
}
// … rest of Walker code …
}
}
NOTE: The leading space before current-menu-item in the str_replace call is intentional — it prevents accidentally matching a class that merely ends in those words. Removing it would cause partial-name collisions on custom class names.