Overview
Use a Tooltip to provide a short hint for an element: what an icon-only button does, the full text behind a truncated label, a keyboard shortcut for a familiar action. The word "nonessential" matters — anything required to complete a task must be visible without hovering or focusing, because touch users cannot hover at all and many people never dwell long enough to trigger the tooltip.
Tooltips are display-only. They close when focus moves on, so they cannot hold buttons, links, or form fields; use a Popover for interactive content opened deliberately.
Anatomy
A tooltip has a trigger and popup content, plus an optional arrow rendered by default. Wrap several tooltips in TooltipProvider to share delay behavior across them. Content should be short — one phrase or sentence — because it appears in small type next to the cursor and disappears as soon as attention moves.
Behavior
Showing. Tooltips appear on hover after a rest delay (600 ms by default) and on keyboard focus with no delay, so keyboard users get hints immediately upon tabbing. Moving between grouped tooltips inside one TooltipProvider skips the wait: once one is visible, adjacent tooltips open instantly, and a closed tooltip reopens instantly if another opens within 400 ms.
Dismissing. Hovering away or blurring the trigger closes the tooltip. Pressing Escape dismisses it too, which matters when a tooltip covers something the person needs to see or click. A tooltip never takes focus itself — it stays purely visual.
Placement. side (top by default), align, and sideOffset sit on TooltipPopup. Set showArrow={false} to remove the arrow. The popup flips and shifts to stay in the viewport.
Accessibility
The trigger keeps its own accessible name and state; the popup gets no ARIA role and no automatic description link. That is intentional — Base UI treats tooltip text as supplementary, because screen readers do not reliably announce hover content. The practical consequence: every trigger must make sense without its tooltip. Icon-only controls need an explicit aria-label, and the tooltip becomes redundant reinforcement rather than the only source of the name.
Keyboard parity is built in: focusing the trigger shows the same tooltip a mouse user sees, and Escape dismisses without moving focus. Because the tooltip does not receive focus, nothing about it appears in the tab order.
Text wraps inside a capped width instead of forming a single long line, so hints remain readable near screen edges. Colors come from theme tokens for dark mode, entrance motion respects reduced-motion preferences through the component's transition classes, and placement uses logical sides that respect document direction.
Installation
Usage
import {
Tooltip,
TooltipPopup,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";<Tooltip>
<TooltipTrigger render={<Button variant="secondary" />}>
Hover me
</TooltipTrigger>
<TooltipPopup>Helpful hint</TooltipPopup>
</Tooltip>Grouping Tooltips
To group multiple tooltips so they appear instantly after the first one is opened, wrap them in TooltipProvider. The grouping logic ensures that once a tooltip becomes visible, the adjacent tooltips will be shown instantly.
<TooltipProvider>
<Tooltip>
<TooltipTrigger render={<Button variant="secondary" />}>
Tooltip 1
</TooltipTrigger>
<TooltipPopup>Content 1</TooltipPopup>
</Tooltip>
<Tooltip>
<TooltipTrigger render={<Button variant="secondary" />}>
Tooltip 2
</TooltipTrigger>
<TooltipPopup>Content 2</TooltipPopup>
</Tooltip>
</TooltipProvider>Don't do this
Nesting the trigger inside a button
// Bad
<Button>
<TooltipTrigger>Save</TooltipTrigger>
</Button>// Good
<Tooltip>
<TooltipTrigger render={<Button variant="secondary" />}>
Save
</TooltipTrigger>
<TooltipPopup>Save changes to this draft</TooltipPopup>
</Tooltip>TooltipTrigger renders a real <button> element, so wrapping one button inside another produces invalid HTML that browsers repair unpredictably — splitting event handling and breaking the accessibility tree. Use the render prop to merge the trigger onto the Button itself.
Hiding essential information behind the tooltip
// Bad
<Input aria-label="API key" />
<Tooltip>
<TooltipTrigger aria-label="Help"><InfoIcon /></TooltipTrigger>
<TooltipPopup>Your API key is shown only once.</TooltipPopup>
</Tooltip>// Good
<Input aria-label="API key" />
<p className="text-muted-foreground text-xs">
Your API key is shown only once.
</p>Touch users can never hover, keyboard users lose the message the moment focus moves on, and screen readers may not announce it at all. If acting incorrectly causes data loss or confusion, the warning belongs in permanent, visible text.
Putting controls inside a tooltip
// Bad
<TooltipPopup>
<p>Draft saved</p>
<Button size="sm">Undo</Button>
</TooltipPopup>// Good
<Popover>
<PopoverTrigger render={<Button variant="ghost" />}>
Draft saved
</PopoverTrigger>
<PopoverPopup>
<PopoverClose render={<Button variant="secondary" />}>Undo</PopoverClose>
</PopoverPopup>
</Popover>A tooltip closes when focus leaves the trigger, so a control inside it cannot be reached: tabbing toward it dismisses the surface that contains it. Interactive follow-up actions belong in a Popover, which holds focus predictably until dismissed.
Examples
Icon-only button
The aria-label names the control; the tooltip repeats it visually for sighted users.
Keyboard shortcut hint
Shortcuts are the classic tooltip use: visible context for people who already know the action.
Grouped toolbar
Inside one TooltipProvider, moving across the toolbar swaps tooltips instantly instead of waiting out the delay each time.
API reference
All parts forward their matching Base UI Tooltip props. Honest UI adds presentation shortcuts on TooltipPopup:
TooltipProvider accepts shared delay, closeDelay, and timeout (400 ms by default) that govern grouping behavior for every tooltip beneath it. The root supports controlled open / onOpenChange and a disabled prop. TooltipContent aliases TooltipPopup.
See the Base UI Tooltip API.