Skip to content

Navigation inside a core/pattern in an overlay template part escapes nested-overlay suppression #82286

Description

@Jiwoon-Kim

Description

When a Navigation Overlay template part contains a core/pattern block, and that pattern
contains a core/navigation block, the inner Navigation is not suppressed. It renders its
own responsive overlay inside the outer overlay, and it renders as a <nav> landmark
nested inside the outer <nav>.

Both outcomes are things the renderer explicitly tries to prevent.

WP_Navigation_Block_Renderer::disable_overlay_menu_for_nested_navigation_blocks() walks
the parsed block tree of the overlay template part and rewrites every core/navigation
it finds:

if ( 'core/navigation' === $block['blockName'] ) {
	$block['attrs']['overlayMenu']                  = 'never';
	$block['attrs']['_isWithinOverlayTemplatePart'] = true;
}

core/pattern is a self-closing node in that tree — its contents do not exist until render
time — so a Navigation inside one is invisible to this walk.

The same file already knows about this timing difference elsewhere. Close-button detection
was moved to the rendered HTML precisely so it would keep working with patterns
(#76567 / #76585):

// Check if overlay contains a navigation-overlay-close block
// (detect in rendered HTML so it works with patterns).

The nested-Navigation suppression did not get the same treatment.

Step-by-step reproduction instructions

  1. Create a Navigation Overlay template part.
  2. Inside it, place a synced pattern (or any core/pattern) whose content is a Navigation
    block with Overlay Menu set to Always.
  3. Assign that template part as a Navigation block's overlay.
  4. Open the overlay on the front end.

Reduced to the two code paths, on WordPress 7.1 with no plugins active:

$inner_nav = '<!-- wp:navigation {"overlayMenu":"always","ariaLabel":"Inner"} -->'
	. '<!-- wp:navigation-link {"label":"Deep","url":"/d"} /-->'
	. '<!-- /wp:navigation -->';

register_block_pattern( 'probe/inner-nav', array( 'title' => 'Inner', 'content' => $inner_nav ) );

// A: Navigation written directly into the overlay content.
$direct  = '<!-- wp:navigation-overlay-close /-->' . $inner_nav;
// B: the same Navigation reached through a pattern.
$pattern = '<!-- wp:navigation-overlay-close /--><!-- wp:pattern {"slug":"probe/inner-nav"} /-->';

// Run each through disable_overlay_menu_for_nested_navigation_blocks(), then render.

Screenshots, screen recording, code snippet

Counting what the walk suppressed, and what the result renders:

Navigation directly in overlay   suppressed=1   rendered <nav>=0   overlay container survives=no
Navigation via core/pattern      suppressed=0   rendered <nav>=1   overlay container survives=YES
  • suppressed — core/navigation blocks the walk found and rewrote
  • rendered <nav> — <nav> elements in the output, which is <div> when
    _isWithinOverlayTemplatePart is set
  • overlay container survives — presence of wp-block-navigation__responsive-container

Environment info

  • WordPress 7.1, no plugins active, Twenty Twenty-Five
  • Also present in Gutenberg trunk: disable_overlay_menu_for_nested_navigation_blocks()
    is unchanged there

Please confirm that you have searched existing issues in the repo.

  • Yes

Please confirm that you have tested with all plugins deactivated except Gutenberg.

  • Yes

Please confirm which theme type you used for testing.

  • Block
  • Classic
  • Hybrid (e.g. classic with theme.json)
  • Not sure

Activity

  1. added
    [Type] BugAn existing feature does not function as intended
    on Sep 1, 2026
  2. Jiwoon-Kim commented on Sep 1, 2026

    @Jiwoon-Kim
    ContributorAuthor

    Two separate consequences, in case they want splitting:

    1. Nested overlay. The inner Navigation keeps overlayMenu: "always", so it renders a
      second responsive container and toggle inside the open overlay.
    2. Nested landmark. Without _isWithinOverlayTemplatePart, the renderer's
      $tag_name = $is_within_overlay ? 'div' : 'nav'; picks nav, producing a <nav>
      inside a <nav>.

    The narrow fix is presumably to expand patterns before the walk, or to apply the same
    rendered-HTML strategy the close button already uses. Which one is right depends on whether
    the attributes need to be rewritten before render, which they currently do.

  3. shrivastavanolo commented on Sep 4, 2026

    @shrivastavanolo
    Contributor

    Confirmed, this reproduces. Traced it down and also verified it with a quick PHPUnit test against wp-env.

    The core issue is that disable_overlay_menu_for_nested_navigation_blocks() in index.php walks the parsed block tree of the overlay template part and rewrites any core/navigation it finds (sets overlayMenu to never and marks it _isWithinOverlayTemplatePart). But core/pattern blocks are still leaf nodes at that point — the pattern hasn't been expanded yet. It only gets expanded later, in a totally separate render pass: render_block_core_pattern() just runs do_blocks() on the raw pattern content, completely outside the tree the walk already touched. So any nav inside a pattern never gets rewritten and renders with its original attrs — hence the nested <nav> and the second overlay container.

    To reproduce:

    1. Register a pattern whose content is a core/navigation block with overlayMenu: "always" (a plain Navigation block with a link inside is enough).
    2. Create a Navigation Overlay template part containing a core/navigation-overlay-close block followed by a core/pattern reference to that pattern.
    3. Assign that template part as the overlay on an outer Navigation block with overlayMenu: "always", and render it.
    4. For comparison, do the same thing again but paste the inner nav directly into the template part instead of going through the pattern.

    I did this with a temporary test added to class-wp-navigation-block-renderer-test.php (basically copied the existing shortcode-in-overlay test), building both template parts and diffing the rendered output:

    Direct case: 1 <nav>, no duplicated responsive-container.
    Pattern case: 2 <nav> elements and the responsive-container shows up twice — same numbers as in the issue writeup.

    (Removed the test afterward, it was just to confirm — tree's clean.)

    Agree with the direction in the description: the close-button check already had to solve this exact problem (patterns not existing yet in the parsed tree) by checking the rendered HTML instead of the parsed blocks. Seems like the fix here should do the same thing — process the rendered overlay output after patterns are expanded, rather than trying to catch it in the pre-render walk.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    [Block] NavigationAffects the Navigation Block[Status] In ProgressTracking issues with work in progress[Type] BugAn existing feature does not function as intended

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions