microsoft / microsoft/fluentui

[Bug]: Escape in a Menu nested in a groupper/modalizer escapes the groupper

Open
#36,315 0 comments 0 reactions 1 assignee View on GitHub

@layershifter is already working on this.

Since Jun 16, 2026.

Component: Menu Fluent UI react-components (v9) Type: Bug :bug:
Dominant language
TypeScript
Stars
20.3k
Forks
2.9k
Avg merge
2d 9h
Merged PRs (30d)
46

Description

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
  1. Focus the "message" group (a groupper + modalizer) — this activates the modal scope.
  2. Move focus into the actions popover and onto the "More options" Menu trigger.
  3. Press to open the menu (focus moves to the first item).
  4. Press Esc.
  5. 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
  • Fix: #36313 — adds focusable.ignoreKeydown: { Escape } on the MenuPopover (mirrors #36275).
  • Same class of bug, fixed for react-combobox: #36275.
  • Prior art on Menu's restorer behaviour: #32840.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.