Tooltip
A label for the thing under the pointer — and the one here with an arrow.
src/components/rahti_ui/tooltip.rs
Basic
A control, and a label for it.
shadcn's own demo. The tooltip is a `popover` in the top layer — never clipped by an ancestor, dismissed by an outside click or `Escape`, and placed by CSS anchor positioning. None of that is code here.
It opens the moment the pointer arrives. Base UI's own `delay` is 600ms, but shadcn's `TooltipProvider` sets `delay = 0` and its docs tell you to put that provider in your root layout — so the tooltip anybody using shadcn actually gets is instant, and zero is the default here for the same reason.
Tab to the button and it opens on focus; click it and it closes. A tooltip lives on controls that do things, and a label still hanging over one after the click is in the way. That is why this component alone has no `popovertarget` fallback — the click would fight it.
The trigger carries `aria-describedby`, which is the whole of the accessibility contract: the tooltip *describes* the control. That is also why nothing inside one may be interactive — a screen reader reads it as part of the trigger, and the pointer only reaches it when a page has set a close delay.
Sides
Where the label opens.
`top` is the default, which is shadcn's for this component and different from the Popover's and the Hover Card's `bottom`. The four sides are placed twice over: an inline `position-area` where the browser has CSS anchor positioning, and the script measuring the trigger everywhere else.
The offset that separates the tooltip from its trigger is a margin on one axis only — `margin-block` above or below, `margin-inline` beside. All four margins would also push an aligned tooltip four pixels off the edge it lines up with.
On icon buttons
The case the component exists for.
What the component is really for: an icon button whose only label is its tooltip. Sweep across the three and each one is instant, because the delay is zero rather than because anything clever is happening.
Every tooltip here asks for `popover="hint"` at mount, which is the platform's own value for this: showing a hint does not dismiss an open `auto` popover the way another `auto` would, so a tooltip on a control inside an open menu no longer shuts the menu. An unsupported value would silently degrade to `manual` and lose light dismiss and `Escape`, so the script reads the value back and reverts rather than serving it and hoping.
The delays
Zero by default, and where a different number goes.
`delay` and `closeDelay` are props of the root and of the `TooltipProvider` above it, which is where Base UI puts them. The Hover Card puts its two on the *trigger* instead — the two components disagree upstream, so they disagree here.
The order is root, then provider, then zero. A root always renders a pair because the server cannot see up the tree, so the script is told separately whether the root meant it or merely rendered the default — that is what lets the first tooltip here inherit while the second overrides.
The third has a close delay, and that is the only thing that makes a tooltip hoverable: with the default zero the pointer can never reach it across the four-pixel gap, which is fine, because nothing in a tooltip is meant to be reached.
The arrow, and the flip
The reason `data-side` here has to tell the truth.
Every other component in this library treats `data-side` as what the page asked for. A tooltip cannot: `position-try-fallbacks` flips it near a viewport edge, and an arrow still glued to the requested side would then point away from its trigger, floating over nothing. It is the most visible wrong thing this library could render.
So the script measures where the tooltip actually landed on every open and writes the real side onto the content and the arrow. The slide-in direction gets corrected by the same write, for free. Narrow the window until one of these has to flip and watch the arrow move with it.
The arrow is rendered by `TooltipContent` rather than exposed as a part, exactly as shadcn's component renders its own: it is not a decision a call site should have to make, and getting it wrong is very visible. shadcn's values are kept — a `size-2.5` square, `rotate-45`, offset two pixels into the tooltip so no seam shows.
The port
What crossed, and what the popover made unnecessary.
The tooltip at the top of this page carries `id="tooltip-hover-demo"`, which is what its trigger's `aria-describedby` names. A component that renders its children twice — as the Sidebar does for its mobile drawer — would otherwise put two tooltips in one document under one id; the script renames the second at mount and repoints the trigger with it.
A tooltip under a root and a provider that both say nothing opens after 0ms and closes after 0ms — shadcn's numbers rather than Base UI's 600 and 0, because shadcn's provider overrides the first and its docs put that provider in the root layout.