Tooltip
StableA short label on an inverse pill that names a control, usually an icon button, and can show its keyboard shortcut. It grows out of its trigger on hover or keyboard focus, and never holds anything you need to read to get on.
Basics
Wrap the control in a Tooltip and give it content. The pill grows out of its trigger in 140ms, drifting 3px away from it, with no arrow: the growth shows where it came from. Hover or tab along the toolbar.
In both themes
Held open for review: real tooltips, one with keycaps and one long enough to wrap. It is an inverse pill: graphite with frost text in light, and pale with ink text in dark, so it stands off either page. One line is an exact pill; wrapped text becomes a softly rounded rectangle.
Sides
side picks where it prefers to open: top (the default), right, bottom or left. It flips to the other side when there is no room and keeps 8px inside the window. Use right beside a folded sidebar and bottom under a header.
Alignment
align lines the pill up with the start, center (the default) or end of its trigger. It matters on wide triggers.
Shortcuts
shortcut adds keycaps after the text, restyled for the inverse pill. Keys take friendly names that read right on every platform: mod is ⌘ on a Mac and Ctrl elsewhere, and shift, alt, enter, esc and the arrows become their symbols on a Mac. Single letters are capitalised. The caps are only drawn: bind the keys in the app.
Long text
A tooltip stops at 260px and wraps, balancing its lines. Keep it to a short phrase; a sentence is the most it should ever hold.
Delay
A tooltip waits 350ms under a resting pointer before it opens, so sweeping across the page does not set off a trail of them. delayDuration changes the wait for one tooltip; 0 opens it at once. Keyboard focus never waits.
Provider
TooltipProvider shares timing between the tooltips inside it. Once one has opened, moving to a neighbour within 150ms (skipDelayDuration) opens it straight away with a quick fade instead of the grow, which is what makes a toolbar feel quick. Wrap the app once; these docs do. A tooltip outside any provider brings its own with the same defaults.
Shared timing: sweep along, and each next tooltip opens at once
skipDelayDuration={0}: each one waits its full delay
Disabled
disabled keeps a tooltip closed without unmounting anything, so the trigger keeps focus. Use it while a menu from the same trigger is open, so the label does not sit on top of the menu. A tooltip with empty content never opens either, which suits a label that only sometimes applies.
Keyboard and touch
How a tooltip opens and closes beyond the mouse.
| What happens | |
|---|---|
| Keyboard focus | Opens at once, with no delay. Moving focus away closes it. |
| Tabbing to a trigger off screen | The trigger scrolls into view and its tooltip stays open. (The underlying Radix tooltip closes when its trigger scrolls; this one stays open when keyboard focus has only just opened it.) |
| Escape | Closes it and leaves focus on the trigger. |
| Pressing the trigger | Closes it, and a click does not reopen it until the pointer leaves and comes back. |
| Moving onto the tooltip | It stays open, so it can be read at a large zoom. disableHoverableContent on the provider turns this off. |
| Touch | Never opens: there is no hover, and a tap runs the control. Anything only in a tooltip is lost on a phone. |
When not to use one
- Never put essential information in a tooltip: why something is off, a price, an error or a warning belongs on the page.
- Nothing interactive inside. A link or a button in a tooltip cannot be reached.
- A few words in sentence case, with no full stop. If it needs a second sentence, it wants to be on the page.
- Do not repeat a visible label. A button that says "Export" needs no tooltip saying "Export".
- Match the tooltip to the icon button's label, so what people see and what screen readers say agree.
Accessibility
- Built on Radix Tooltip. While it is open the tooltip has
role="tooltip"and the trigger points at it witharia-describedby, so its text is read as the trigger's description. The trigger's own name (anIconButton'slabel) is still what names it. - It opens on keyboard focus as well as hover, closes on Escape, and stays open while the pointer moves onto it.
- Symbol keycaps carry spoken names, so ⌘ is read as "Command".
- The trigger must take a ref and props: the kit's buttons do. Wrap anything else in an element that can.
- With reduced motion it fades in and out without growing or drifting.
Props
Tooltip
| Prop | Type | Default | Description |
|---|---|---|---|
contentRequired | ReactNode | None | What it says. Keep it short; nothing interactive. Empty content never opens. |
childrenRequired | ReactElement | None | The trigger. It must take a ref and props, as the kit's buttons do. |
side | 'top' | 'right' | 'bottom' | 'left' | 'top' | Preferred side. Flips when there is no room. |
align | 'start' | 'center' | 'end' | 'center' | Which part of the trigger it lines up with. |
shortcut | string[] | None | Keycaps after the text, e.g. ['mod', 'K']. |
disabled | boolean | false | Keeps it closed, e.g. while a menu from the same trigger is open. |
delayDuration | number | None | Hover wait in ms for this tooltip. Defaults to the provider's 350. |
open | boolean | None | Controlled open state. |
defaultOpen | boolean | false | Starting state when uncontrolled. |
onOpenChange | (open: boolean) => void | None | Called when it opens or closes. |
sideOffset | number | 6 | Gap from the trigger in px. |
collisionPadding | number | 8 | Space kept from the edges of the window. |
TooltipProvider
| Prop | Type | Default | Description |
|---|---|---|---|
childrenRequired | ReactNode | None | The app, or a toolbar. |
delayDuration | number | 350 | Hover wait in ms before the first tooltip opens. |
skipDelayDuration | number | 150 | Window in ms in which moving to another trigger opens its tooltip at once. |
disableHoverableContent | boolean | false | Close as soon as the pointer leaves the trigger, even towards the tooltip. |