Combination of Toolbar and Tooltip causes glitching on Tooltip

Author: KoenCaCreated Aug 21, 2026Updated Sep 9, 2026

Provide a general summary of the issue here

I have a use case where I use the Toolbar component with buttons that each have a tooltip. When I tab into the toolbar and then tab again to exit it, then the tooltip of the last button flashes briefly and the tooltip of the button that had focus is rendered at the top left of the viewport.

Expected Behavior?

When I tab out of the toolbar the last tooltip should not be shown and the tooltip of the first button should not be shown in the top left corner of the viewport.

Current Behavior

I've created a reproduction in Stackblitz: reproduction

I also made a screen recording showcasing the issue:

https://github.com/user-attachments/assets/9545fd74-9c56-4142-b848-57cb89be8d82

Possible Solution

I analysed the issue with AI and it came up with following explanation and work around. The work around is visual and doesn't fix the issue, but makes it less noticeable at least.

Cause

useToolbar's Tab handler calls focusManager.focusLast() and returns without preventDefault, so the last control is focused before the browser carries focus onward. The tooltip's focus path opens immediately, useTooltipTrigger.onFocus calls state.open(true), and the subsequent blur closes it, so an open and a close arrive back to back within one keydown.

A tooltip overlay then ends up rendered while the trigger state is closed. In that state useOverlayPosition.updatePosition returns early on !isOpen, and placement is position?.placement ?? null, so the overlay is never measured. That produces exactly the observed DOM: no inline position, no data-placement, painted at the document origin.

What keeps the node alive afterwards is not confirmed. One candidate is useExitAnimation, whose useAnimation layout effect has deps [ref, isActive, onEnd]. All three are stable, so the effect runs at most once per exit and a missed onEnd would strand the node.

Workaround

Hide the overlay whenever the placement render prop is null, which is exactly the condition "mounted but never measured". React Aria already treats placement as the readiness signal, isEntering is useEnterAnimation(tooltipRef, !!placement).

typescript
<Tooltip className={({ placement }) => (placement ? "" : "invisible")} />

Use visibility: hidden, not display: none. updatePosition measures the overlay with getBoundingClientRect, so removing it from layout gives a wrong position on the normal open path, where the first commit also has placement === null for one frame.

This suppresses the misplaced tooltip in both presentations. It does not address the underlying state, and the last control's tooltip still flashes briefly on the way out, since that one is genuinely measured before it closes.

https://github.com/user-attachments/assets/d239961f-67a7-4910-a74d-8924f2d24fdd

Context

For work I'm building a "bulk action bar" that will be overlayed over a table when multiple items are selected. The buttons are small icon buttons that get a tooltip to explain the behavior of each button.

️ Steps to Reproduce

I've created a reproduction in Stackblitz: reproduction

Version

react-aria-components 1.20.0

What browsers are you seeing the problem on?

Chrome

If other, please specify.

No response

What operating system are you using?

MacOS

Your Company/Team

No response

Tracking Issue

No response