Skip to content

[toast] Viewport stays expanded after the hovered toast is removed by the pointer #6162

Open

Description

@jonmilner

Bug report

Current behavior

When the toast under the mouse is removed by the mouse itself (a click on its close button, or a swipe), the viewport stays expanded (data-expanded) after the pointer leaves it. The stack collapses only after the pointer enters a toast again and leaves.

Steps, with three toasts stacked (timeout: 0), in Chrome with a real mouse:

  1. Hover the top toast. The viewport expands.
  2. Remove it with the mouse: click its Toast.Close, or swipe it away.
  3. Move the pointer off the viewport, e.g. to the page's top-left corner.
  4. The viewport still has data-expanded (checked 600ms later).

Expected behavior

The viewport collapses once the pointer has left it, as it does when no toast was removed.

Reproducible example

https://codesandbox.io/p/sandbox/kw2pmc

Steps are on the page. It needs real pointer input: synthetic events don't show it.

It also depends on layout. In the sandbox, the toasts sit in normal flow in a column anchored to the bottom of the viewport, so after the hovered toast is removed, nothing moves under the pointer. With the column anchored to the top, the next toast slides under the pointer, Chrome fires the mouseout, and the click case collapses as expected. The swipe case stays stuck either way.

Base UI version

v1.9.0

Which browser are you using?

Chrome (Chromium via Playwright)

Which OS are you using?

macOS

Which assistive tech are you using (if applicable)?

None

Additional context

What we see in the event log: after the hovered toast's element is removed, the next mouse move dispatches mouseover on the element now under the pointer (outside the viewport), with a relatedTarget still inside the React tree, and no mouseout from the viewport. React's enter/leave plugin ignores a mouseover whose relatedTarget it manages and waits for the matching mouseout, which never comes, so ToastViewport's onMouseLeave (handleMouseLeave) never runs and hovering stays true.

Our workaround: a document listener that tracks whether the pointer was last seen inside the viewport; when the next mouseover lands outside it with no leave in between, it dispatches a mouseout on the viewport (relatedTarget set to the new target), which React turns into the missed onMouseLeave.

A fix in Base UI might listen for native pointerleave/mouseleave on the viewport, or re-check whether the pointer is still over the viewport (e.g. on the next pointermove on the document) after a toast is removed while hovering is set.

Activity

  1. lord-0011 commented on Nov 7, 2026

    @lord-0011

    Hi! I reproduced the close-button case with @base-ui/react 1.9.0 and real mouse input (Playwright): after closing the hovered toast and moving the pointer away, the viewport keeps data-expanded (3/3 runs; hover-and-leave without removing a toast collapses normally). I couldn't reproduce the swipe case in my setup.

    I'd like to fix it: while the viewport is expanded, listen for mouseover on the document, and if the pointer lands outside the viewport while hovering is still set, run the existing mouse-leave handling (so the transition and touch-swipe deferrals still apply). With that change the stack collapses in all my runs. I'd add a regression test to ToastViewport.test.tsx. Does that approach work for you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    component: toastChanges related to the toast component.status: waiting for maintainerThese issues haven't been looked at yet by a maintainer.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions