DismissableLayer 1.1.19: Escape during layer registration dismisses the layer underneath (stacked Dialogs both close)
Packages: @radix-ui/react-dismissable-layer 1.1.19 (via @radix-ui/react-dialog 1.1.23). Not reproducible with react-dismissable-layer 1.1.11 / react-dialog 1.1.15.
Describe the bug
With two stacked modal Dialogs (A open, B opened from A), pressing Escape while B is still registering its DismissableLayer closes both A and B. With the older versions, an Escape in that window is ignored entirely (neither dialog closes); B closes only once its registration has propagated. Either way, A is never dismissed.
What changed upstream
PR #3968 (commit 37f522f2) moved the "is this the highest layer" check from the keydown handler into render. There is a window after B's layer has been added to the layer set but before A's effect has re-run: A's useEscapeKeydown listener is still attached and still considers itself the top layer, so an Escape in that window dismisses A as well as B. Because the listeners are attached on document in the capture phase, an app-level handler on the dialog content cannot intercept it.
Reproduction (jsdom / Testing Library, deterministic)
// Two modal Dialogs; the second one is opened from a button inside the first.
// Listen for Radix's internal 'dismissableLayer.update' event: it fires when B's
// layer is added to the set. Dispatch Escape right then.
document.addEventListener('dismissableLayer.update', () => {
document.querySelector('#input-in-dialog-b')
?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }));
}, { once: true });
fireEvent.click(screen.getByRole('button', { name: 'Open B' }));
// expected: Dialog A still open (B either still open, as in 1.1.11, or closed)
// actual (1.1.19): both closed — onOpenChange(false) fires on AAlso reproducible in a browser (Playwright, 344 px viewport) with a fast open-then-Escape sequence.
Expected behavior
Escape dismisses only the top-most layer, regardless of whether the new layer's registration has fully propagated.
Suggested fix
Guard the escape handler with the layer set at event time (as before #3968), or defer attaching the new layer's keydown listener until the previous top layer has re-rendered with the updated index.
Source: radix-ui/primitives