Skip to content

[Bug]: NavigationMenu: unmount-on-hide="false" causes all dropdowns except the last to close on any click #2608

@Predota

Description

@Predota

Environment

- Operating system: Linux 5.0 (Macintosh; Intel Mac OS X 10_15_7) – Chrome 147.0.0.0  
- CPU: Intel Core i9-9880H @ 2.30GHz (8 cores)  
- Node.js: v22.22.0  
- Nuxt CLI: 3.34.0  
- Package manager: pnpm 8.15.6  
- Nuxt: 4.4.2  
- Nitro: 2.13.3  
- Builder: Vite 7.3.2  
- Config flags: compatibilityDate, devtools, modules  
- Modules: reka-ui/nuxt 2.9.6

Link to minimal reproduction

https://stackblitz.com/edit/github-whqv4pfb?file=app%2Fcomponents%2FNavigation.vue

Steps to reproduce

  1. Open Dropdown 1 (hover or click the trigger)
  2. Click the blue area inside the content (not the red link)
  3. Actual: Dropdown 1 closes immediately
  4. Expected: Dropdown 1 stays open (clicking inside content should not dismiss it)
  5. Now open Dropdown 2 and click its blue area, it correctly stays open

Describe the bug

When unmountOnHide is false, NavigationMenuContent passes force-mount to Presence, which keeps all NavigationMenuContentImpl instances mounted, including closed ones. Each instance wraps a DismissableLayer that registers itself in the global layers set on mount and only deregisters on unmount.

When the user clicks anywhere, every mounted DismissableLayer evaluates the click. Closed dropdowns have pointer-events: none, so the click is never detected as "inside" their layer. This causes each closed dropdown's DismissableLayer to fire pointerDownOutsideinteractOutsidedismiss.

The handleDismiss function in NavigationMenuContentImpl unconditionally dispatches EVENT_ROOT_CONTENT_DISMISS, which calls menuContext.onItemDismiss(), resetting modelValue to "" and closing whichever dropdown was actually open.

The last dropdown appears unaffected because there are no closed-but-mounted DismissableLayer instances after it in the set to erroneously trigger a dismiss.

Suggested fix

Guard handleDismiss in NavigationMenuContentImpl so that only the currently active content can trigger dismissal:

function handleDismiss() {
  if (menuContext.modelValue.value !== itemContext.value) return
  // ... existing dismiss logic
}

This ensures closed (but still mounted) content panels ignore the dismiss event entirely.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions