Skip to content

fix(react-headless-components-preview): let the consumer popover prop win on the PopoverSurface, MenuPopover and Listbox slots - #36665

Open
Ray Knight (ArrayKnight) wants to merge 3 commits into
microsoft:masterfrom
ArrayKnight:fix/headless-popover-attribute-36647
Open

fix(react-headless-components-preview): let the consumer popover prop win on the PopoverSurface, MenuPopover and Listbox slots#36665
Ray Knight (ArrayKnight) wants to merge 3 commits into
microsoft:masterfrom
ArrayKnight:fix/headless-popover-attribute-36647

Conversation

@ArrayKnight

@ArrayKnight Ray Knight (ArrayKnight) commented Aug 31, 2026

Copy link
Copy Markdown

Three hooks in the headless package set the native popover attribute on their surface slot on the wrong side of the props spread, so a consumer's own popover value can never win: usePopoverSurface pins it after ...props, useMenuPopover spreads popover: 'auto' over the base state's root, and useListbox assigns state.root.popover = 'auto' after the hook returns. Since popover="auto" is the browser's light-dismiss mode — opening one auto popover closes every other — an application that needs two surfaces open at once (a popover inside a popover, a persistent side surface) has no way to reach popover="manual". Measured on an 8-cell probe: 1/8 surfaces open before, 8/8 after.

The fix moves the attribute to the default side of the merge at all three sites, the position the package's own slot-default convention already provides, so the consumer's value merges over it. A regression test is added at each site; each was verified to fail without the fix. (The surface id and role, which are pinned deliberately for the trigger's aria-details contract, are intentionally not touched.)

Fixes #36647.

Extracted from #36656 per maintainer request — each in-tree fix from that PR as an isolated change.

… win on the PopoverSurface slot

The surface slot spelled its popover attribute after the consumer props
spread, so the prop was silently discarded — popover exclusivity then capped
a page at one open Popover where eight should show, and a consumer switching
to manual dismiss was ignored. It moves into the slot's defaultProps, the
placement the Tooltip hook already uses: the hook supplies the default, the
consumer wins.

The id spelled alongside it deliberately stays put. Popover.types documents
`<Popover id>` as the surface's id channel, and usePopoverTrigger points
aria-details at that same value, so letting a surface-level id win would
leave the trigger referencing an element that no longer exists.
PopoverSurface.test.tsx already guards that linkage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
… win on the MenuPopover and Listbox slots

Both surfaces pinned their popover attribute where a consumer's prop could
not reach it. useMenuPopover spread `popover: 'auto'` over the base state's
root after the base hook had already merged props, and useListbox assigned
`state.root.popover = 'auto'` outright after its hook returned. Because every
auto popover on a page closes the others, that capped a page at one open Menu
and one open listbox: measured, three listboxes give one open surface, and
three Menus give one. The default now merges before the base state rather than
over it, which is the order slot.always already uses for defaultProps — the
hook supplies 'auto', a consumer spelling popover="manual" wins, and the same
three-cell pages then show three open surfaces.

Nothing else rides either assignment. Unlike PopoverSurface, where an id was
spelled alongside the attribute and had to stay put, these two sites set the
popover attribute alone: the listbox's id and its aria-labelledby linkage are
owned by useListboxSlot, which this does not touch.

Light dismiss under the default is unchanged, and a closed listbox still stays
mounted at display:none.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aj9uA3rCVgosnh2zNn8qkc
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.

[Bug]: headless Popover/Menu/Listbox pin the popover attribute after the props spread, so only one surface can be open per page

1 participant