Tooltip
A non-modal text label anchored to its trigger. It never takes focus.
Content is supplementary, never essential
The content is supplementary, never essential: a label for an icon-only control, a keyboard shortcut typed into the string, or one clarifying sentence. Interactive content, links, and images are banned. A tooltip is not reachable on touch and not reachable by a screen reader that never focuses its trigger, so a reason, a result, or an error never lives in one; it goes inline.
No touch or long-press support, by decision.
Open delay and the second trigger
TooltipProvider fixes a 500ms open delay and a 300ms skip delay, mounted once at the app root. Hover the first icon and count to the open; move to the second within 300ms of the first closing and it opens at once, with no second count. The window is deliberately short: a longer one makes the delay feel arbitrary, because a hover that follows any recent close — including one a scroll caused — would skip the count while an isolated hover would not.
One Radix limitation shows here. Its hoverable-content grace area tracks the pointer through a document listener that resolves a frame late, so a flick fast enough to fire only two or three pointer events across the row leaves the first tooltip stranded and the second unopened until the pointer moves again. Hoverable content stays on regardless: WCAG 2.1 SC 1.4.13 requires that a pointer be able to move onto the tooltip without it disappearing.
Any focusable child
children is one element with asChild forced, so a tooltip wraps anything, not only a Button. The child must be focusable — the component does not inject a tabIndex. Avatar takes no focus of its own, so this example adds one at the call site.
Disabled trigger
Disabled trigger: not supported. A disabled button fires no pointer or focus events. The reason a control is disabled is essential information, so it goes inline. No span-wrapper recipe: it ships a focusable element with no accessible name.
Keyboard and dismiss
Keyboard focus on the trigger opens the tooltip; Tab away closes it. Escape closes it, innermost first inside a dialog. Any pointer-down closes it too, and the pointer may cross the 8px gap from the trigger onto the tooltip without it closing. Radix sets aria-describedby on the trigger and renders a role="tooltip" node — the trigger keeps its own accessible name, and the tooltip only describes it.
Surface and motion
The chip inverts to bg-foreground text-background with no border and no arrow: light is white text on neutral-950, dark is neutral-950 text on neutral-50. It enters and exits on the floating item's anchored pair, scaling from 0.96 with the Radix popper transform origin, so content that flips still grows from its trigger.
Accessibility
Both surfaces meet WCAG AA contrast in light and dark mode. The tooltip never takes focus, so it draws no ring of its own — the trigger keeps whichever ring it already has.