Tooltip
A short ink label that names a control on hover or keyboard focus. It describes; it never holds the only copy of something, since touch never opens it. Source: src/components/ui/tooltip.tsx.
Default
The common case: an icon-only button with an aria-label, and a tooltip that repeats it for pointer users. Tab onto a button to open it from the keyboard; Esc closes it.
With a shortcut
Put the key combination after the label in Kbd. Inside a tooltip, Kbd switches to a translucent cap that reads on ink.
Sides
Top by default. Radix flips it when there is no room.
Delay
Timing lives on TooltipProvider. The component's own default is 0ms; the app root wraps everything in a 300ms provider, so 300ms is what ships. Move along a row: once one tooltip has opened, its neighbours open at once.
On a disabled control
A disabled button gets no hover or focus, so wrap it in a focusable span and put the trigger on that. Say why it is disabled.
Motion and layering
Known gap: the zoom and slide play on every open, including the instant re-opens while moving along a toolbar. The better pattern (delay first, then appear without animation while warm) is not built yet.
| Value | Token | Usage |
|---|---|---|
| 50 | Same layer as popovers. The brand ladder (--z-tooltip 300) is defined but not wired in yet. | |
| 140ms | Fade, scale from 95% and an 8px slide, on every open. | |
| 90ms | Fade and scale out. | |
| quart-out | The entrance curve. | |
| ink | Ink card; --foreground and --muted-foreground are remapped so nested text and Kbd read on it. | |
| 282px | Width cap; longer text wraps. Corners rounded-lg (10px), sideOffset 6. |
Props
| Part / prop | Type | Default | Description |
|---|---|---|---|
| TooltipProvider | Radix Tooltip.Provider | — | Shared timing for every tooltip inside it. The app root already renders one. |
| delayDuration | number | 0 | Hover time before opening. The root provider passes 300. |
| skipDelayDuration | number | 300 | Radix: after a tooltip closes, others open instantly for this long. |
| Tooltip | Radix Tooltip.Root | — | open, defaultOpen, onOpenChange, delayDuration (per tooltip). |
| TooltipTrigger | Radix Tooltip.Trigger | — | Use asChild around a focusable element. Opens on hover and on keyboard focus. |
| TooltipContent | Radix Tooltip.Content | — | Portals itself, pointer-events none. Takes side, align, collisionPadding. No arrow is rendered. |
| sideOffset | number | 6 | Gap from the trigger, in px. |
Best practices
When to use
- Every icon-only button gets one, repeating its aria-label.
- Don't tooltip a button whose text already says the same thing; add a shortcut or say nothing.
- Anything richer than a line (a link, an avatar, a preview) is a HoverCard or a Popover.
Behavior
- Keep the root provider's 300ms. A nested delayDuration={0} is for dense toolbars only.
- Tooltips never contain interactive content; the content is pointer-events none.
- Touch devices never show them, so the information must exist elsewhere.
Accessibility
- The tooltip text is exposed as the trigger's description; the aria-label is still its name.
- It opens on keyboard focus and closes on Esc without moving focus.
- A trigger must be focusable. Wrap disabled controls in a span with tabIndex={0}.
Content
- A few words in sentence case, no full stop: “Copy message”, “Pin thread”.
- Shortcuts go in Kbd after the label, one key per cap.