Repository navigation
feat(react-headless-components-preview): move headless overlays onto useTopLayer - #36910
Draft
Victor Genaev (mainframev) wants to merge 2 commits into
Draft
Victor Genaev (mainframev) wants to merge 2 commits into
Victor Genaev (mainframev) wants to merge 2 commits into
Conversation
Victor Genaev (mainframev)
added this pull request to stack #36911
October 16, 2026 15:39
|
Pull request demo site: URL |
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
from
October 16, 2026 16:00
adef1ae to
25fbb78
Compare
Victor Genaev (mainframev)
removed this pull request from stack #36911
October 16, 2026 16:00
Victor Genaev (mainframev)
changed the base branch from
feat/top-layer-subpath
to
feat/top-layer-engine
October 16, 2026 16:00
Victor Genaev (mainframev)
added this pull request to stack #36912
October 16, 2026 16:00
Comment on lines
+45
to
+47
| preventEscapeClose: true, | ||
| onOpen: isNonModal ? event => requestOpenChange({ type: 'surfaceToggle', open: true, event }) : undefined, | ||
| onClose: isNonModal ? ({ event }) => requestOpenChange({ type: 'surfaceToggle', open: false, event }) : undefined, |
Contributor
Author
There was a problem hiding this comment.
A non-modal dialog opened or closed by a popovertarget button is still reported as surfaceToggle, same as master.
Modal and alert dialogs keep blocking the native Escape close (preventEscapeClose), because onOpenChange can veto a close with preventDefault. Everything else using the hook lets the browser close on Escape.
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
from
October 16, 2026 16:06
25fbb78 to
914745f
Compare
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
from
October 16, 2026 16:38
914745f to
7c71087
Compare
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
from
October 16, 2026 20:55
7c71087 to
440b719
Compare
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
3 times, most recently
from
October 18, 2026 13:17
46e6d88 to
d4a2f58
Compare
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
from
October 18, 2026 18:24
d4a2f58 to
363e15e
Compare
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
2 times, most recently
from
October 18, 2026 18:31
15c560a to
49e17b6
Compare
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
from
October 18, 2026 18:37
49e17b6 to
b70a7dc
Compare
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
2 times, most recently
from
October 19, 2026 13:49
2f596df to
7acb450
Compare
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
2 times, most recently
from
October 19, 2026 16:38
c347169 to
2bc59a7
Compare
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
2 times, most recently
from
October 19, 2026 21:27
d9c4077 to
cd60bec
Compare
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
3 times, most recently
from
October 21, 2026 09:33
345c66c to
44e6139
Compare
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
2 times, most recently
from
October 21, 2026 10:17
5050196 to
9dd2d71
Compare
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
2 times, most recently
from
October 21, 2026 16:06
fefb7a1 to
2b587fd
Compare
…the top-layer hooks
…use it in Popover and Dialog Adds lifecycle callbacks (onBeforeShow, onShow, onBeforeHide, onHide) to the open-state hooks and a shared useLayerFocusReturn hook built on them. Popover returns focus to its trigger and non-modal Dialog to the element focused before opening, replacing the per-component focus code.
Victor Genaev (mainframev)
force-pushed
the
feat/top-layer-overlays
branch
from
October 21, 2026 20:38
2b587fd to
848e960
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Previous Behavior
Popover, Dialog, Tooltip, MenuPopover, the Combobox/Dropdown/TagPicker listbox and Toaster each drive the top layer on their own, and disagree on details: when they run, whether browser closes reach React state, focus restore, and exit animations.
New Behavior
All of them use
useTopLayerfrom the previous PR. Each keeps its own focus model.usePopoverstill drivescontentRef, the surface still unmounts after the browser reports the close (now also after exit animations), and focus still returns to the trigger when it was inside. The tests from fix: restore headless TeachingPopover close focus #36843 pass unchanged.preventEscapeClose) soonOpenChangecan veto a close; non-modal dialogs still reportpopovertargettoggles assurfaceToggle.hintwithhintFallback: 'manual', so in browsers withoutpopover="hint"showing a tooltip never closes an open popover.autofocusitem and trigger restore, active descendant, toast restore).Visible changes, also in the change file:
trapFocusPopover and Drawer keep Tab inside.[autofocus]or its first focusable element on open, and restores focus on close only when it was inside.DialogSurfacecloses only the inner one.inertandaria-hiddenwhile hidden;aria-labelledby/aria-describedbystill resolve.The spec gets a "Changes from master" table with every difference.
Bugs fixed
Master had a separate top-layer implementation in each overlay, and they disagreed on edge cases. Moving them onto the shared hooks fixes:
<form method="dialog">ordialog.close()from outside React closed the dialog but never reachedonOpenChange, soopenstayedtrue. It is now reported assurfaceToggle.showModal()from outside React or acommandforinvoker could open a closed modal Dialog that stays mounted (unmountOnClose={false}) without React knowing. Where dialogs firetoggle(Chromium 132+), the open is now reported assurfaceToggle, and undone when the consumer keepsopenfalse. Non-modal Dialog already synced these.DialogSurface.inertandaria-hidden="true"until the exit ends, then the consumer's own values come back.DialogSurfaceclosed both. Only the inner one closes now; Escape from content portaled out of the surface still closes the dialog.Related Issue(s)
Part 2 of 2 of the headless top-layer stack.