[menu] Always highlight the first item on open#4816
Conversation
Bundle size
PerformanceTotal duration: 1,126.39 ms +34.24 ms(+3.1%) | Renders: 50 (+0) | Paint: 1,731.60 ms +67.81 ms(+4.1%)
11 tests within noise — details Check out the code infra dashboard for more information about this PR. |
✅ Deploy Preview for base-ui ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Hey @chsmc-ant 👋
This part won't work. The current behaviour is correct, at least by default.
It's the same across Base UI, React Aria, Ariakit, and Radix. The reason is that keyboard activation is a commitment to operate the menu. Pointer activation is often just revealing the menu before the user has chosen where to go. When the user opens by keyboard, focus should enter the composite widget because keyboard users need a focused item to continue. When the user opens by pointer, focus does not need to jump to an arbitrary item because the pointer itself is doing the navigation. |
|
Hey @colmtuite that makes sense and thank you for the response! I opened up this PR as an alternative approach to #4815 which I think is a better way to make the desired behavior for us possible. |
When a menu is opened programmatically (via controlled
openor by callingonOpenChange(true)from a custom interaction that isn't one ofMenu.Trigger's built-in open keys),useListNavigationleavesactiveIndexasnull. Its defaultfocusItemOnOpen: 'auto'only highlights an item when Floating UI's own trigger handlers set the internalkeyRef/focusItemOnOpenRef— which doesn't happen for external opens. Every item then renders withtabindex="-1",FloatingFocusManagerparks focus on the popup container, and the user needs an extra↓press before arrow navigation works.This PR passes
focusItemOnOpen: truetouseListNavigationinMenuRoot, so the first item is highlighted on open regardless of how the menu was opened.ArrowUp-on-trigger continues to highlight the last item becausekeyRefstill takes precedence insideuseListNavigation's initial-sync effect.Behavior change
This also means pointer-driven opens (mouse/touch click on the trigger) now highlight the first item instead of leaving the popup container focused. Two existing tests have been updated to reflect that:
MenuRoot.test.tsx— removed oneArrowDownfrom the nested-menu outside-click test (item 1 is now focused after click).Menubar.test.tsx— the "keyboard navigation after mouse click" test now asserts item 1 is focused immediately, without a leadingArrowDown.Related: #4815 adds an imperative
actionsRef.setActiveIndexescape hatch as a non-breaking alternative; this PR is the broader behavior fix and can land independently or instead of it.