Tooltip
A short supplementary label that appears when a pointer rests on a control or a keyboard moves focus to it, for information a reader can do without.
Preview
tooltip · base base · style base-lyraOpen under the product themeInstallation
pnpm dlx shadcn@latest add @opsinjs/tooltip
The @opsinjs namespace is declared in your components.json. Everything it installs is code you then own. There is no runtime package to keep in step.
Usage
import { Tooltip } from "@/components/ui/tooltip"<Tooltip content="A rolling mean across the nights you have logged.">
<button type="button">Overnight average</button>
</Tooltip>When to use it
Use it when
- Expanding an icon-only control's name, already carried through aria-label.
- Adding a short optional note beside a label that already stands on its own.
- Surfacing a value's full form on demand, where the reading is already on screen.
Do not use it when
- The label carries something a touch reader needs. A phone has no hover, so the tooltip never opens for them.Use
calloutinstead. - You are explaining what a clinical word means. That belongs inline, in the accessibility tree.Use
terminstead. - You are attaching a hint to a form control, such as a date format. A field hint is always visible.Use
fieldinstead.
Anatomy
TooltipThe root. Owns open state, delay and portal.Controlled bycontentTooltip.TriggerThe caller's own focusable element, keeping its own aria-label.Controlled bychildrenTooltip.PopupA raised card at the footnote step, portalled against the preferred side.Controlled bysideTooltip.ArrowA rotated square in the popup's card fill.
- Tooltip 1Provider plus root
- Tooltip.Trigger 1data-slot="tooltip-trigger"
- Tooltip.Popup 1data-slot="tooltip-popup", portalled
- Tooltip.Arrow 1data-slot="tooltip-arrow"
Examples
On an icon button
Each icon button carries its own name through aria-label; content repeats it
visibly. The labels name fictional actions.
tooltip-on-an-icon-button · base base · style base-lyraOpen under the product themeA supplementary hint
The setting is named in plain words, so a touch reader needs no hover, and
content adds one optional sentence.
tooltip-a-supplementary-hint · base base · style base-lyraOpen under the product themeContent guidelines
Keep a tooltip to a phrase in sentence case, with no terminal punctuation unless it is a full sentence. Write what the control does or means, never an instruction that only appears on hover.
Do
"Reminders wait until the morning during the hours you set here" beside a labelled setting.
Don’t
The only label on an icon-only control, shown on hover alone. A touch reader sees a bare glyph.
Accessibility
Audited against WCAG 2.2 AA: a source pass over the whole rubric, and a rendered
pass over the measured findings. Author-run, not independent; clinical review
is still pending. The rendered pass found no axe violation, no target-size
breach, no focus-visible gap and no clipping at 320px. The source pass found
nothing to change in tooltip.tsx.
What holds:
- Base UI wires the popup through
aria-describedby, so the label is a description, never a replacement for the control's own name. - Focus opens the tooltip, Escape dismisses it without moving focus, and the label follows focus off the trigger.
- No status colour, no colour literal, no ramp step:
footnotestep, nopxsize, neither colour axis.
Open:
- A tooltip is unreachable on a hover-less touch device, so
contentstays supplementary. - The popup's non-text contrast (SC 1.4.11) is the shared
border-borderrole token, reasoned rather than measured. - The popup staying hoverable across the gap (SC 1.4.13) is a Base UI 1.7.0 default, not a manual trace.
| Key | Action | Notes |
|---|---|---|
| Tab | Moves focus onto the trigger and shows the tooltip | No open delay on focus. |
| Shift+Tab | Moves focus off the trigger and hides the tooltip | The label follows focus. |
| Escape | Dismisses the tooltip | Focus stays on the trigger and reopens it on the next pointer rest. |
| Enter, Space | Activates the underlying control | The tooltip binds neither key. |
| Pair | Theme | APCA Lc | WCAG 2.2 | Floor |
|---|---|---|---|---|
| body text on the page | light | 101.6 | 17.18:1 | Pass |
| body text on the page | dark | -100.5 | 18.00:1 | Pass |
| body text on a card | light | 104.7 | 17.96:1 | Pass |
| body text on a card | dark | -99.6 | 16.32:1 | Pass |
| body text on the muted ground | light | 98.1 | 16.32:1 | Pass |
| body text on the muted ground | dark | -97.5 | 13.77:1 | Pass |
| secondary text on the page | light | 83.9 | 7.42:1 | Pass |
| secondary text on the page | dark | -80.7 | 13.44:1 | Pass |
| secondary text on a card | light | 87.0 | 7.76:1 | Pass |
| secondary text on a card | dark | -79.8 | 12.19:1 | Pass |
| secondary text on the muted ground | light | 80.4 | 7.05:1 | Pass |
| secondary text on the muted ground | dark | -77.8 | 10.28:1 | Pass |
| a hairline boundary on the page | light | 19.3 | 1.41:1 | Below floor |
| a hairline boundary on the page | dark | -8.2 | 1.90:1 | Below floor |
| a hairline boundary on a card | light | 22.4 | 1.47:1 | Below floor |
| a hairline boundary on a card | dark | -7.3 | 1.72:1 | Below floor |
| a hairline boundary on the muted ground | light | 15.8 | 1.34:1 | Below floor |
| a hairline boundary on the muted ground | dark | 0.0 | 1.45:1 | Below floor |
| a placeholder boundary on the page | light | 69.8 | 4.52:1 | Pass |
| a placeholder boundary on the page | dark | -51.0 | 7.62:1 | Pass |
| a placeholder boundary on a card | light | 72.8 | 4.72:1 | Pass |
| a placeholder boundary on a card | dark | -50.1 | 6.91:1 | Pass |
| a placeholder boundary on the muted ground | light | 66.2 | 4.29:1 | Pass |
| a placeholder boundary on the muted ground | dark | -48.1 | 5.83:1 | Pass |
| a placeholder fill on the page | light | 8.3 | 1.18:1 | Below floor |
| a placeholder fill on the page | dark | -8.2 | 1.90:1 | Below floor |
| a placeholder fill on a card | light | 11.4 | 1.23:1 | Below floor |
| a placeholder fill on a card | dark | -7.3 | 1.72:1 | Below floor |
| a placeholder fill on the muted ground | light | 0.0 | 1.12:1 | Below floor |
| a placeholder fill on the muted ground | dark | 0.0 | 1.45:1 | Below floor |
| a placeholder boundary on its own fill | light | 58.9 | 3.84:1 | Pass |
| a placeholder boundary on its own fill | dark | -41.5 | 4.01:1 | Below floor |
| the card hairline on the page | light | 69.8 | 4.52:1 | Pass |
| the card hairline on the page | dark | -28.7 | 4.19:1 | Below floor |
| the card hairline on a card | light | 72.8 | 4.72:1 | Pass |
| the card hairline on a card | dark | -27.9 | 3.80:1 | Below floor |
| body text on the canvas material | light | 104.7 | 17.96:1 | Pass |
| body text on the canvas material | dark | -100.5 | 18.00:1 | Pass |
| body text on the inset material | light | 98.1 | 16.32:1 | Pass |
| body text on the inset material | dark | -100.1 | 17.18:1 | Pass |
| body text on the card material | light | 104.7 | 17.96:1 | Pass |
| body text on the card material | dark | -99.6 | 16.32:1 | Pass |
| body text on the raised material | light | 104.7 | 17.96:1 | Pass |
| body text on the raised material | dark | -97.5 | 13.77:1 | Pass |
| body text on the sheet material over the darkest backdrop | light | 99.9 | 16.76:1 | Pass |
| body text on the sheet material over the darkest backdrop | dark | -99.7 | 16.47:1 | Pass |
| body text on the sheet material over the lightest backdrop | light | 104.7 | 17.96:1 | Pass |
| body text on the sheet material over the lightest backdrop | dark | -80.8 | 6.07:1 | Pass |
| body text on the overlay material over the darkest backdrop | light | 85.9 | 13.48:1 | Pass |
| body text on the overlay material over the darkest backdrop | dark | -99.9 | 16.77:1 | Pass |
| body text on the overlay material over the lightest backdrop | light | 104.7 | 17.96:1 | Pass |
| body text on the overlay material over the lightest backdrop | dark | -54.2 | 2.69:1 | Below floor |
These are the measured token pairs this component draws colour from, not a measurement of the component itself.
API reference
Prop
Type
Generated from TooltipProps in registry/bases/base/tooltip.tsx.
content is required; an empty one raises a development warning. children
must be focusable. side defaults to top and Base UI flips it at the screen
edge. delay defaults to 600 and governs the pointer only; focus opens the
tooltip at once.
Related
Spinner
An indeterminate loading ring for a brief in-place wait whose shape is unknown, reached for only where a skeleton cannot say what is arriving.
Popover
A small panel anchored to the control that opened it, floated in a portal and dismissed by leaving it, built as a thin themed wrapper over Base UI Popover.