Overlays & navigation
Tooltip
Tooltips name or explain a control in a few words. They appear after a short delay on hover and immediately on keyboard focus, move instantly from one control to the next along a toolbar, stay open while the pointer is over them, and close with Escape. Keep them short and never put anything interactive inside; use a Popover for that.
<div role="toolbar" aria-label="Formatting" className="flex flex-wrap items-center gap-0.5 rounded-ui-lg border border-line bg-surface p-1 shadow-ui"> {tools.map((t) => ( <Tooltip key={t.label} content={t.label} shortcut={t.shortcut} asLabel> <Button size="icon-sm" variant="ghost"> {t.icon} </Button> </Tooltip> ))}</div>Usage
import { Tooltip } from "@/components/ui/tooltip"; <Tooltip content="Archive invoice"> <Button size="icon" variant="ghost" aria-label="Archive"><Archive strokeWidth={1.75} /></Button></Tooltip>Examples
Toolbar
With asLabel the tooltip text becomes the icon button's accessible name, so you don't repeat it in aria-label. The shortcut is shown after the label.
<div role="toolbar" aria-label="Formatting" className="flex flex-wrap items-center gap-0.5 rounded-ui-lg border border-line bg-surface p-1 shadow-ui"> {tools.map((t) => ( <Tooltip key={t.label} content={t.label} shortcut={t.shortcut} asLabel> <Button size="icon-sm" variant="ghost"> {t.icon} </Button> </Tooltip> ))}</div>Placement
Top is the default. Tooltips flip to the opposite side near the edge of the screen.
{(["top", "bottom", "start", "end"] as Placement[]).map((p) => ( <Tooltip key={p} content={`Placed ${p}`} placement={p}> <Button variant="outline" className="capitalize">{p}</Button> </Tooltip>))}Delay
The hover delay keeps tooltips from flashing while the pointer passes over the page. Keyboard focus always shows them right away.
<Tooltip content="Shows right away" delay={0}> <Button variant="outline">No delay</Button></Tooltip><Tooltip content="Shows after 400 ms"> <Button variant="outline">Default</Button></Tooltip><Tooltip content="Shows after 1 second" delay={1000}> <Button variant="outline">Slow</Button></Tooltip>Names on avatars
A common use: full names on a compact avatar stack.
<div className="flex justify-center -space-x-1.5"> {team.map((name) => ( <Tooltip key={name} content={name} placement="bottom" asLabel> <button type="button" className="rounded-full ring-2 ring-surface"> <Avatar name={name} decorative /> </button> </Tooltip> ))}</div>Props
contentrequiredReactNodedefault: —The text. Keep it to a few words.childrenrequiredReactElementdefault: —One focusable element, such as a Button. It receives aria-describedby (or aria-labelledby).placement"top" | "bottom" | "start" | "end"default: "top"Preferred side. Flips when there is no room.align"start" | "center" | "end"default: "center"Alignment along the trigger.delaynumberdefault: 400Hover delay in milliseconds. Focus shows the tooltip immediately.shortcutstringdefault: —Keyboard hint shown after the text, e.g. ⌘B.asLabelbooleandefault: falseUse the text as the trigger's accessible name instead of its description.
Accessibility
- The tooltip has role="tooltip" and is linked to its trigger with aria-describedby, or aria-labelledby when asLabel is set.
- It opens on keyboard focus (focus-visible) as well as hover, and Escape hides it without moving focus.
- Moving the pointer onto the tooltip keeps it open, so it can be read at any zoom level (WCAG 1.4.13).
- Tooltips never open on touch, so don't hide information there that people on phones need; show it in the UI or use a Popover.
- Never put links or buttons inside a tooltip: it can't receive focus.