Provide a general summary of the issue here
Since react-aria-components 1.17.0, pressing the underlay of a dismissable ModalOverlay makes the modal scroll/jump before it closes. It happens in Chrome and Safari, not in Firefox. It is a side effect of #9768, which removed the underlay's pointerdown preventDefault.
🤔 Expected Behavior?
Dismissing a modal by clicking the underlay should look the same as closing it with a button inside the dialog: no scroll jump, and the exit animation starts from the current position.
😯 Current Behavior
Setup: a dismissable ModalOverlay that is the scroll container (overflow-y: auto), a Modal with a top offset (margin-top: 5dvh), and dialog content taller than the viewport.
- On mousedown on the underlay, the overlay scrolls down by the modal's top offset — the modal visibly jumps to the top of the screen.
- On mouseup the modal closes, and the exit animation plays from the shifted position.
Chrome and Safari only. Firefox is not affected. No errors are thrown.
StackBlitz: https://stackblitz.com/edit/ctk9t6xd-8dzuz6ca?file=src%2FExample.tsx
💁 Possible Solution
Cause: mousedown on the non-focusable underlay moves focus to <body> (Chrome/Safari do this, Firefox does not). The FocusScope containment then re-focuses the Dialog with a plain element.focus(), and the browser scrolls the dialog into view inside the scrollable overlay.
Until 1.16, useOverlay's underlayProps called preventDefault on pointerdown, which kept focus in the dialog. #9768 removed that (so fields commit on outside click), which exposed this.
Suggested fix: restore focus without scrolling in the containment focusout handler (focusWithoutScrolling / focus({ preventScroll: true })). That keeps the #9768 fix and removes the jump.
Workaround we ship: onPointerDown/onMouseDown on ModalOverlay that call preventDefault when target === currentTarget (the pre-1.17 behavior). It is included as commented-out props in the repro — uncomment to see the jump disappear.
🔦 Context
Our app uses fullscreen route modals: the ModalOverlay is the scroll container and the Modal sits 5dvh below the top with a slide-out-to-bottom exit animation. Since 1.17.0, every backdrop dismissal first jumps the modal to the top and then animates it out from the wrong position. Users see this on every modal close.
🖥️ Steps to Reproduce
https://stackblitz.com/edit/ctk9t6xd-8dzuz6ca?file=src%2FExample.tsx
- Open the modal. Don't click or scroll inside it.
- Mousedown on the dark underlay and hold → the modal jumps to the top (the overlay scrolls by the modal's 5dvh offset).
- Release → the modal closes from the shifted position.
Compare: closing with the ✕ button inside the dialog has no jump.
Version
react-aria-components 1.19.0 (regression started in 1.17.0)
What browsers are you seeing the problem on?
Chrome
If other, please specify.
Safari
What operating system are you using?
macOS
🧢 Your Company/Team
No response
🕷 Tracking Issue
No response
Provide a general summary of the issue here
Since react-aria-components 1.17.0, pressing the underlay of a dismissable
ModalOverlaymakes the modal scroll/jump before it closes. It happens in Chrome and Safari, not in Firefox. It is a side effect of #9768, which removed the underlay's pointerdownpreventDefault.🤔 Expected Behavior?
Dismissing a modal by clicking the underlay should look the same as closing it with a button inside the dialog: no scroll jump, and the exit animation starts from the current position.
😯 Current Behavior
Setup: a dismissable
ModalOverlaythat is the scroll container (overflow-y: auto), aModalwith a top offset (margin-top: 5dvh), and dialog content taller than the viewport.Chrome and Safari only. Firefox is not affected. No errors are thrown.
StackBlitz: https://stackblitz.com/edit/ctk9t6xd-8dzuz6ca?file=src%2FExample.tsx
💁 Possible Solution
Cause: mousedown on the non-focusable underlay moves focus to
<body>(Chrome/Safari do this, Firefox does not). TheFocusScopecontainment then re-focuses theDialogwith a plainelement.focus(), and the browser scrolls the dialog into view inside the scrollable overlay.Until 1.16,
useOverlay'sunderlayPropscalledpreventDefaulton pointerdown, which kept focus in the dialog. #9768 removed that (so fields commit on outside click), which exposed this.Suggested fix: restore focus without scrolling in the containment focusout handler (
focusWithoutScrolling/focus({ preventScroll: true })). That keeps the #9768 fix and removes the jump.Workaround we ship:
onPointerDown/onMouseDownonModalOverlaythat callpreventDefaultwhentarget === currentTarget(the pre-1.17 behavior). It is included as commented-out props in the repro — uncomment to see the jump disappear.🔦 Context
Our app uses fullscreen route modals: the
ModalOverlayis the scroll container and theModalsits 5dvh below the top with a slide-out-to-bottom exit animation. Since 1.17.0, every backdrop dismissal first jumps the modal to the top and then animates it out from the wrong position. Users see this on every modal close.🖥️ Steps to Reproduce
https://stackblitz.com/edit/ctk9t6xd-8dzuz6ca?file=src%2FExample.tsx
Compare: closing with the ✕ button inside the dialog has no jump.
Version
react-aria-components 1.19.0 (regression started in 1.17.0)
What browsers are you seeing the problem on?
Chrome
If other, please specify.
Safari
What operating system are you using?
macOS
🧢 Your Company/Team
No response
🕷 Tracking Issue
No response