Skip to content

fix(navigation-menu): keep dropdown open on click after hover-open#3882

Open
BnayaZil wants to merge 2 commits into
radix-ui:mainfrom
BnayaZil:fix/navigation-menu-stick-on-hover-then-click
Open

fix(navigation-menu): keep dropdown open on click after hover-open#3882
BnayaZil wants to merge 2 commits into
radix-ui:mainfrom
BnayaZil:fix/navigation-menu-stick-on-hover-then-click

Conversation

@BnayaZil

Copy link
Copy Markdown

Summary

Fixes a behavior bug in NavigationMenu: a trigger that the user has hovered (which opens the dropdown) gets closed if the user then clicks it. The click should be a no-op — leaving the dropdown open — which is what Base UI's NavigationMenuTrigger does via Floating UI's useClick({ stickIfOpen, toggle }).

Repro

In any NavigationMenu story (e.g. NavigationMenu / Basic):

  1. Hover a trigger → dropdown opens (correct)
  2. Click that same trigger → dropdown closes (wrong — should stay open)

Root cause

NavigationMenuTrigger fires onTriggerEnter on onPointerMove (which sets value = itemValue) and fires onItemSelect on onClick (which toggles value based on prevValue === itemValue). So the sequence "hover → click" first opens via hover, then the toggle on click — having no notion of how the menu was opened — closes it.

Fix

Reuse the existing hasPointerMoveOpenedRef (the per-trigger ref the component already keeps to suppress a different hover-vs-click race in onPointerMove). In onClick, if the trigger is currently open and hasPointerMoveOpenedRef.current is true, swallow the click and clear the ref. A subsequent click — without an intervening pointer-leave + re-hover — falls through to the normal toggle path. This mirrors Base UI's stickIfOpen semantics without touching the provider-level state machine or the public API.

The change is one if block in one handler in packages/react/navigation-menu/src/navigation-menu.tsx.

Verified regressions

A new Cypress spec (cypress/e2e/NavigationMenu.cy.ts) locks the behavior. All five cases pass with the fix; the two "sticks open" cases fail without it (3 pass / 2 fail), confirming the test captures the bug:

  • hover → click on same trigger keeps dropdown open (the fix)
  • click on closed trigger opens it (regression check)
  • click on click-opened trigger toggles closed (toggle still works)
  • hover A → hover B opens B and closes A (cross-trigger hover still works)
  • hover → leave → re-hover → click sticks open (hasPointerMoveOpenedRef reset on pointerLeave is the right reset point)

Also verified:

  • pnpm --filter @radix-ui/react-navigation-menu lint
  • pnpm --filter @radix-ui/react-navigation-menu typecheck
  • pnpm --filter @radix-ui/react-navigation-menu build
  • pnpm test (vitest, 233 passing)

Previously, hovering a trigger opened the dropdown and a subsequent
click on the same trigger closed it, because onItemSelect toggled
based solely on whether the current open value matched the trigger's
value — with no notion of how the menu was opened. Swallow the click
when the dropdown was just opened by hover (using the existing
hasPointerMoveOpenedRef signal), matching Base UI's stickIfOpen
behavior. A click on an already-click-opened trigger still toggles
closed; a click on a closed trigger still opens it.
@changeset-bot

changeset-bot Bot commented May 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0c18635

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@radix-ui/react-navigation-menu Patch
radix-ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Spell out the why behind the fix (the toggle had no notion of how the
menu was opened) and frame the Base UI reference as relevant to folks
comparing primitives in the shadcn ecosystem, rather than as a generic
appeal to Base UI's behavior.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant