Skip to content

[Tooltip] Close when a scroll moves the trigger away from the cursor - #49108

Open
stop1love1 wants to merge 2 commits into
mui:masterfrom
stop1love1:fix/tooltip-close-on-scroll
Open

[Tooltip] Close when a scroll moves the trigger away from the cursor#49108
stop1love1 wants to merge 2 commits into
mui:masterfrom
stop1love1:fix/tooltip-close-on-scroll

Conversation

@stop1love1

Copy link
Copy Markdown

Closes #44548

Problem

Hover a tooltip trigger, then scroll without moving the mouse: the trigger slides out from under the cursor, but the browser fires no pointer event, so onMouseLeave never runs and the tooltip stays open over unrelated content.

Repro from the issue — hover a tooltip, scroll away, the tooltip is still there.

This matches what was agreed in the issue thread:

Closing when the cursor leaves the trigger no matter the reason (scroll, actual cursor move) feels more correct to me. — @mj12albert

I agree, closing the Tooltip when scrolling away seems a better default. — @aarongarciah

and the ARIA pattern quoted there: the tooltip "remains open as long as the cursor is over the trigger or the tooltip". React Aria and Radix already behave this way.

Solution

handleMouseOver already funnels every open path, so it records where the cursor was — and records null when the event was not a mouseover, which is exactly the focus and touch cases.

While the tooltip is open, a capture-phase scroll listener on the document (mirroring the existing Escape listener right above it) closes the tooltip when that remembered point is no longer over the trigger nor over the tooltip. It goes through handleMouseLeave, so leaveDelay and onClose behave exactly as they do when the cursor physically leaves.

Deliberately unchanged:

  • Opened by focus — the tooltip stays as long as the trigger is focused, per the ARIA pattern.
  • Opened by touch — there is no cursor to leave behind.
  • Cursor still on the trigger — a small scroll that keeps the pointer inside the trigger does not close it.
  • Cursor on the tooltip — an interactive tooltip under the pointer stays open.

The listener only exists while a tooltip is open, and in practice the first scroll event removes it again.

Testing

Four tests in a new scroll block. The first one fails on master (expected +0 to equal 1onClose is never called) and passes with the fix; the other three lock in the cases that must not change.

Test Files  1 passed (1)
     Tests  86 passed | 18 skipped (104)

The whole @mui/material suite is unchanged: 4687 passed on this branch vs 4683 passed on master (the 4 new tests), with the same single pre-existing Select failure — does not select an option when the opening mouseup lands on it before the drag delay — present on both, and passing in isolation either way.

pnpm eslint, pnpm typescript and prettier --check are clean on the touched files.

Scrolling moves the trigger out from under a motionless cursor without the browser
firing any pointer event, so the tooltip stayed open with nothing hovered.

Remember where the cursor was when it entered the trigger, and while the tooltip is
open, close it on a scroll that leaves the cursor outside both the trigger and the
tooltip. Tooltips opened by focus or by touch have no cursor to leave behind and are
left untouched.

Closes mui#44548
@code-infra-dashboard

code-infra-dashboard Bot commented Sep 7, 2026

Copy link
Copy Markdown

Deploy preview

https://deploy-preview-49108--material-ui.netlify.app/
QR code for https://deploy-preview-49108--material-ui.netlify.app/

Bundle size

Bundle Parsed size Gzip size
@mui/material 🔺+439B(+0.08%) 🔺+119B(+0.08%)
@mui/lab 0B(0.00%) 0B(0.00%)
@mui/private-theming 0B(0.00%) 0B(0.00%)
@mui/system 0B(0.00%) 0B(0.00%)
@mui/utils 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

The tests only stubbed the trigger rect, so whether the cursor counted as being over
the tooltip depended on where Popper laid it out, which in turn depends on the
rendered size of the tooltip. The tooltip wraps differently on the CI font stack than
it does locally, and the popper ended up covering the simulated cursor there.

Stub every rect the component reads and keep the whole scenario away from the viewport
origin so no real pointer of the test browser can interfere.
@zannager zannager added the scope: tooltip Changes related to the tooltip. label Sep 8, 2026
@zannager
zannager requested a review from siriwatknp September 8, 2026 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: tooltip Changes related to the tooltip.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Tooltip] Stays visible when scrolling away

2 participants