Skip to content

Navigation Link: fix current-menu-item not applied for custom kind links, legacy blocks, and taxonomy links without explicit kind#76485

Open
getdave wants to merge 12 commits intotrunkfrom
fix/navigation-link-current-menu-item-cpt
Open

Navigation Link: fix current-menu-item not applied for custom kind links, legacy blocks, and taxonomy links without explicit kind#76485
getdave wants to merge 12 commits intotrunkfrom
fix/navigation-link-current-menu-item-cpt

Conversation

@getdave
Copy link
Copy Markdown
Contributor

@getdave getdave commented Mar 13, 2026

What

Fixes #61266

Fixes current-menu-item and aria-current="page" not being applied to Navigation Link blocks in several scenarios that should produce an active state.

Why

The PHP render function used a fragile dynamic property lookup to determine whether a nav link is active:

$kind      = empty( $attributes['kind'] ) ? 'post_type' : str_replace( '-', '_', $attributes['kind'] );
$is_active = ! empty( $attributes['id'] )
    && get_queried_object_id() === (int) $attributes['id']
    && ! empty( get_queried_object()->$kind ); // ← dynamic property on queried object

This converts kind from kebab-case to snake-case and then reads a property of that name off the queried object. It works for "post-type" (→ WP_Post->post_type) and "taxonomy" (→ WP_Term->taxonomy), but silently fails for any other value:

  • kind: "custom" — links saved before the current entity-link conventions, or links where the editor resolved a URL to a post ID, store id alongside kind: "custom". The property get_queried_object()->custom never exists, so $is_active is always false.
  • kind absent, type: "category" or type: "tag" — the JS editor omits kind from the stored attributes when newKind is empty and the type is a built-in (see update-attributes.js). Defaulting to "post-type" causes a WP_Post check against a WP_Term archive page.

How

During development, the classic WordPress menu system (_wp_menu_item_classes_by_context() in wp-includes/nav-menu-template.php) was reviewed as a reference for how active-state detection should work and how post/term ID collisions should be prevented. That analysis directly informed the approach taken here and identified the follow-up gaps noted at the bottom of this PR.

Replaces the property-lookup approach with explicit instanceof checks, mirroring how the classic menu system avoids post/term ID collisions:

  • $stored_kind and $stored_type are read from attributes
  • When kind is absent, taxonomy_exists( $resolved_type ) infers whether the link targets a taxonomy — handles category, tag (mapped to post_tag), and any registered custom taxonomy
  • $is_taxonomy_link drives an instanceof WP_Term vs instanceof WP_Post check, replacing the unreliable property lookup
  • $link_id uses is_numeric() to guard against the historical case where id could be a URL string
  • Post/term ID collision protection is fully preserved (a taxonomy link never becomes active on a post page with the same integer ID, and vice versa)

Testing Instructions

  1. Create a page (e.g. "About") and publish it
  2. Add a Navigation block and insert a Custom Link item pointing to that page's URL, using the search/autocomplete to select the page (so an id is stored)
  3. Visit the page on the front end — the nav item should now have current-menu-item and aria-current="page"
  4. Repeat with a Category link and a Tag link — both should activate correctly on their respective archive pages ✅
  5. For custom post types: register a CPT, create a post of that type, add a navigation link to it, and verify the active class appears when viewing that post ✅
  6. Verify no false positives: a category link should not be active when viewing a post whose ID happens to match the category term ID

Backwards Compatibility

Stored kind Queried object Result
"post-type" (page/post/any CPT) WP_Post ✅ Active (unchanged)
"taxonomy" (category/tag/custom taxonomy) WP_Term ✅ Active (unchanged)
"custom" with id (legacy links) WP_Post ✅ Active (bug fixed)
empty / not stored (legacy) WP_Post ✅ Active (backwards compat)
kind absent, type: "category" or "tag" WP_Term ✅ Active (bug fixed via taxonomy_exists() fallback)
"post-type" link on term page with same ID WP_Term ✅ Inactive (collision guard preserved)
"taxonomy" link on post page with same ID WP_Post ✅ Inactive (collision guard preserved)

Tests

24 PHP unit tests added to phpunit/blocks/class-block-library-navigation-link-test.php covering:

  • Active state for pages, standard posts, custom post types, legacy links without kind, and kind: "custom" links with id (the confirmed bug)
  • Active state for categories, tags, and links where kind is absent but type identifies a taxonomy
  • Inactive state for non-matching pages, absent/zero/string IDs
  • Post/term ID collision guards (must pass both before and after the fix)

Known gaps — follow-up issues

As part of this work the classic WordPress menu system (_wp_menu_item_classes_by_context()) was reviewed in detail. It handles four distinct match branches (A–D); this PR addresses Branch B (direct ID + type match). The following gaps should each be tackled as a separate follow-up issue:

  1. core/navigation-submenu has the same bug — the submenu block's index.php contains an identical copy of the broken ->$kind property lookup. The same fix applies. There is also an opportunity to extract the shared $is_active logic into a single utility function rather than maintaining it in two places.

  2. No URL-based matching for links without an id (Branch D) — the classic system matches custom type items by comparing the stored URL against REQUEST_URI. A kind: "custom" link with no id (severed entity links, manually-typed URLs) will never receive current-menu-item even when the URL exactly matches the current page.

  3. No taxonomy ancestor propagation (Branch A) — when viewing a singular post, the classic system flags any category/tag the post belongs to as an active parent, walking the full ancestor chain. The block system has no equivalent.

  4. No ancestor/parent classescurrent-menu-ancestor, current-menu-parent, current-{object}-parent, and related classes from the classic system's second pass are largely absent from the block implementation (the submenu block has partial support via an inner-block scan, but nothing systematic).

getdave added 2 commits March 13, 2026 10:46
…ith id

Replace fragile dynamic property lookup (`get_queried_object()->$kind`) with
explicit `instanceof WP_Post` / `instanceof WP_Term` checks. This fixes
`current-menu-item` and `aria-current="page"` never being applied to links
stored with `kind: "custom"` that carry a numeric `id` (e.g. legacy links and
CPT links), while retaining post/term ID collision protection.

Add 13 PHP unit tests covering active state for posts (page, post, CPT, legacy
without kind, and the confirmed custom-kind bug), active state for terms
(category, tag), inactive states (non-matching page, absent/zero/string id),
and post/term ID collision guards.
When `kind` is not stored in the block attributes (which can happen for
built-in types like 'category' and 'tag' in older blocks or links created via
the Custom Link variation), fall back to `taxonomy_exists()` on the stored
`type` to determine if the link is a taxonomy link. Also map the JS-normalised
'tag' type back to 'post_tag' so the lookup works correctly.

Add three tests: page-type without kind (post-type fallback), category-type
without kind (taxonomy inferred), and tag-type without kind (post_tag alias).
@getdave getdave self-assigned this Mar 13, 2026
@github-actions github-actions bot added the [Package] Block library /packages/block-library label Mar 13, 2026
@getdave getdave added [Type] Bug An existing feature does not function as intended [Block] Navigation Affects the Navigation Block [Block] Navigation Link Affects the Navigation Link Block labels Mar 13, 2026
getdave added 7 commits March 13, 2026 11:12
…vigation_link_is_active()

Moves the active-state determination (Branch B entity ID match + Branch C
post-type archive match) out of the render function into a dedicated helper.
Cleans up the render function and prepares the logic for reuse in the
navigation-submenu block (follow-up).
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 13, 2026

Flaky tests detected in a182987.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/23528854523
📝 Reported issues:

@getdave getdave marked this pull request as ready for review March 13, 2026 14:10
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 13, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @Copilot, @chuckpearson, @jeroensmeets, @voyager131, @wdburgdorf.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: Copilot, chuckpearson, jeroensmeets, voyager131, wdburgdorf.

Co-authored-by: getdave <get_dave@git.wordpress.org>
Co-authored-by: MaggieCabrera <onemaggie@git.wordpress.org>
Co-authored-by: scruffian <scruffian@git.wordpress.org>
Co-authored-by: jhimross <jhimross@git.wordpress.org>
Co-authored-by: Marc-pi <mdxfr@git.wordpress.org>
Co-authored-by: SirLouen <sirlouen@git.wordpress.org>
Co-authored-by: jhmonroe <jhmonroe@git.wordpress.org>
Co-authored-by: mrwweb <mrwweb@git.wordpress.org>
Co-authored-by: shazzad <sajib1223@git.wordpress.org>
Co-authored-by: ethanclevenger91 <eclev91@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes incorrect “current” state detection for core/navigation-link server-side rendering by replacing a fragile queried-object dynamic property check with explicit object type checks and taxonomy inference, restoring current-menu-item and aria-current="page" behavior for legacy/custom scenarios.

Changes:

  • Add block_core_navigation_link_is_active() to determine active state via instanceof WP_Post / instanceof WP_Term and taxonomy_exists() inference when kind is missing.
  • Replace inline active-state logic in render_block_core_navigation_link() with the new helper.
  • Add comprehensive PHP unit tests covering active/inactive cases (custom kind, missing kind, category/tag without kind, collision guards, and post type archives).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
packages/block-library/src/navigation-link/index.php Introduces and uses a dedicated active-state helper with explicit query-object type guards and taxonomy inference.
phpunit/blocks/class-block-library-navigation-link-test.php Adds many new unit tests validating current-menu-item / aria-current behavior across post, term, legacy, and archive scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

public static function wpTearDownAfterClass() {
unregister_post_type( 'dogs' );
getdave and others added 2 commits March 25, 2026 01:21
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…oss-test conflicts

Co-authored-by: getdave <444434+getdave@users.noreply.github.com>
Agent-Logs-Url: https://github.com/WordPress/gutenberg/sessions/fe4ef4be-552a-4651-ab48-5500c3fc8bfa
Copy link
Copy Markdown
Contributor

@MaggieCabrera MaggieCabrera left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me and works as expected

@scruffian
Copy link
Copy Markdown
Contributor

Should we make the same change in navigation-submenu?

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Block] Navigation Link Affects the Navigation Link Block [Block] Navigation Affects the Navigation Block [Package] Block library /packages/block-library [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

current-menu-item does not get applied to custom links in navigation block

5 participants