Tooltip
Inverted label that opens on hover and keyboard focus, in the contrast ink of the look.
Tooltip names or explains a control in a short line of text, for an icon, an abbreviation, or a
shortcut that has no room for a label. It is an inverted chip, bg-fg with text-background,
so it reads as the opposite of the surface it sits on in every look. It opens on hover and on
keyboard focus, and Icon button and
Kbd are built to sit inside it.
Usage
import { Button } from '@/components/ui/button';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
<Tooltip>
<TooltipTrigger asChild>
<Button>Hover me</Button>
</TooltipTrigger>
<TooltipContent>Shortcut: ⌘K</TooltipContent>
</Tooltip>A Tooltip brings its own provider when there is none around it, so it needs no wrapper. TooltipTrigger renders a
<button> unless you set asChild, which puts the trigger behavior on your own element, like
the kit Button above. The content portals to the end of <body> and sets its own font-sans,
so type matches the chip wherever the trigger sits. It is 12px text with px-3 py-1.5,
balanced across lines, with a small arrow, so keep it to a short phrase.
Where it opens
The content opens on the top and flips to the opposite side when there is no room. Set
side on TooltipContent to right, bottom, or left, and sideOffset to move it away
from the trigger. sideOffset is 0 by default, so the arrow touches the trigger. The other
placement props, align and collisionPadding, are Radix's.
Waiting before it opens
A tooltip opens as the pointer arrives. That is a delay of 0, where Radix's own default is
700ms, because a tooltip on an icon should answer right away. Set delayDuration in
milliseconds on Tooltip to wait for the pointer to rest instead, which suits a tooltip on
text that people cross on their way elsewhere. Keyboard focus opens it with no wait at any
setting.
Wrap a row of tooltips in the kit's TooltipProvider to set their delay in one place. Inside
one, each Tooltip uses that provider instead of its own, so once one has opened, the next
opens at once as the pointer moves along the row, until the pointer has been away for
skipDelayDuration, 300ms by default. A delayDuration on a single Tooltip still
overrides the provider's. Below, the first button waits 700ms and the rest open as you reach
them.
Tooltip on a disabled control
A disabled <button> receives no pointer events, so its tooltip cannot open. Where a reason
for the disabled state helps, put the trigger on a wrapper that can take focus, which also lets
a keyboard user reach the reason.
<Tooltip>
<TooltipTrigger asChild>
<span tabIndex={0}>
<Button disabled>Publish</Button>
</span>
</TooltipTrigger>
<TooltipContent>Add a title before publishing</TooltipContent>
</Tooltip>Accessibility
- The trigger is a native
<button>by default, so it takes Tab, and the tooltip opens when it receives keyboard focus and closes when focus leaves. It does not open when the focus came from a pointer press. - Escape closes the tooltip without moving focus. It also closes when the trigger is pressed, when you scroll an ancestor of the trigger, when you press outside it, and when another tooltip opens.
- While it is open the trigger points at it with
aria-describedby, and the content hasrole="tooltip", so a screen reader reads it as a description after the control's own name. A tooltip describes a control and does not name it, so give an icon-only control its ownaria-label. - The pointer can move from the trigger onto the tooltip without closing it, and Radix keeps a grace area across the gap between them, so the text can be selected or read at length.
- It does not open on touch. A tap runs the control and opens nothing, so a touch user never sees the text, and anything they need must be visible without it.
- A tooltip holds text only. It takes no focus and has no interactive content, so a link or a button inside it cannot be reached. Use a Popover for that.
- Under reduced motion the tooltip appears and disappears without its fade.
- It has no forced-colors rule and no border. The system replaces the
bg-fgfill there, so the chip has no edge of its own against the page.
When to use something else
- A visible label, when the control can carry one. A tooltip is hidden from touch and from
anyone who does not hover or tab to the control, so a word beside the icon is the most
reliable name. Use Button with
iconBeforerather than an icon alone. - Hover card for a richer preview, like a person or an issue, with several lines and an avatar. It also opens on hover and not on touch, and its content cannot be reached by keyboard, so it can only add to something already on the page.
- Popover for content a reader opens on purpose and can operate, like a form, a link, or a menu of choices. It opens on press, works on touch and with the keyboard, and keeps focus inside.
- Icon button when the control is an icon that runs an action. It wraps this Tooltip and sets the label as both the tooltip and the accessible name.
Install
shadcn add @gesso/tooltip