Component
Menu (@fluentui/react-menu)
Package version
@fluentui/react-menu@9.25.0 — regression vs 9.21.2.
React version
18.3.1 (also reproduces on React 17 / 19).
Environment
Any browser — this is a Tabster focus-management timing issue, not browser-specific.
Current Behavior
When a Menu is nested inside a Tabster groupper + modalizer (e.g. a focusMode Card, or a list item whose hover/actions popover shares a modalizer id with the item), pressing Escape to close the menu moves focus to the groupper root instead of restoring focus to the menu trigger.
Expected Behavior
Closing the menu with Escape restores focus to the menu trigger (e.g. the "More options" button), as it did in @fluentui/react-menu@9.21.2.
Reproduction
https://github.com/layershifter/fluent-v9-menu-groupper-esc-repro
npm install && npm run dev for the interactive demo (with surfaceMotion toggles), or npm test for the deterministic Playwright assertions.
Steps to reproduce
- Focus the "message" group (a groupper + modalizer) — this activates the modal scope.
- Move focus into the actions popover and onto the "More options"
Menu trigger.
- Press ↓ to open the menu (focus moves to the first item).
- Press Esc.
- Actual: focus lands on the message group. Expected: focus returns to the "More options" trigger.
Root cause
react-menu@9.25.0 wraps the menu popover in a surfaceMotion presence component (presenceMotionSlot, unmountOnExit), which defers the popover unmount by a tick.
Pre-9.25.0 the popover unmounted synchronously on close; removing the focused menu item dropped focus to <body>, which fired Tabster's restorer source → a higher-priority Restorer async-focus intent that pre-empted the groupper's pending EscapeGroupper intent. With the deferred unmount, react-menu moves focus menu-item → trigger directly (never through <body>), so the restorer never arms and the groupper-escape fires and steals focus to the group.
Tabster itself is unchanged between 9.21.2 and 9.25.0 — only the menu's unmount/restore timing changed. surfaceMotion={null} on the <Menu> restores the synchronous unmount and fixes it; surfaceMotion={false} does not (only null is special-cased by presenceMotionSlot).
Note: this is the same class of bug already fixed for Combobox/Dropdown in #36275 (event.stopPropagation() can't stop Tabster because it listens in the capture phase).
Are you reporting an Accessibility issue?
Yes — focus is not restored to the trigger on dismiss (WCAG 2.4.3 Focus Order).
Related
Component
Menu (
@fluentui/react-menu)Package version
@fluentui/react-menu@9.25.0— regression vs9.21.2.React version
18.3.1 (also reproduces on React 17 / 19).
Environment
Any browser — this is a Tabster focus-management timing issue, not browser-specific.
Current Behavior
When a
Menuis nested inside a Tabster groupper + modalizer (e.g. afocusModeCard, or a list item whose hover/actions popover shares a modalizer id with the item), pressing Escape to close the menu moves focus to the groupper root instead of restoring focus to the menu trigger.Expected Behavior
Closing the menu with Escape restores focus to the menu trigger (e.g. the "More options" button), as it did in
@fluentui/react-menu@9.21.2.Reproduction
https://github.com/layershifter/fluent-v9-menu-groupper-esc-repro
npm install && npm run devfor the interactive demo (withsurfaceMotiontoggles), ornpm testfor the deterministic Playwright assertions.Steps to reproduce
Menutrigger.Root cause
react-menu@9.25.0wraps the menu popover in asurfaceMotionpresence component (presenceMotionSlot,unmountOnExit), which defers the popover unmount by a tick.Pre-9.25.0 the popover unmounted synchronously on close; removing the focused menu item dropped focus to
<body>, which fired Tabster's restorer source → a higher-priorityRestorerasync-focus intent that pre-empted the groupper's pendingEscapeGroupperintent. With the deferred unmount, react-menu moves focus menu-item → trigger directly (never through<body>), so the restorer never arms and the groupper-escape fires and steals focus to the group.Tabster itself is unchanged between 9.21.2 and 9.25.0 — only the menu's unmount/restore timing changed.
surfaceMotion={null}on the<Menu>restores the synchronous unmount and fixes it;surfaceMotion={false}does not (onlynullis special-cased bypresenceMotionSlot).Note: this is the same class of bug already fixed for Combobox/Dropdown in #36275 (
event.stopPropagation()can't stop Tabster because it listens in the capture phase).Are you reporting an Accessibility issue?
Yes — focus is not restored to the trigger on dismiss (WCAG 2.4.3 Focus Order).
Related
focusable.ignoreKeydown: { Escape }on theMenuPopover(mirrors fix: Escape in an open Combobox or Dropdown does not trigger tabster actions #36275).