Skip to content

Modal jumps to the top when dismissed via underlay press in Chrome/Safari (regression in 1.17.0) #10324

Description

@philippd

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

  1. Open the modal. Don't click or scroll inside it.
  2. Mousedown on the dark underlay and hold → the modal jumps to the top (the overlay scrolls by the modal's 5dvh offset).
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions