tooltip
A tooltip is a small bubble with a short detail that people can do without, such as what a figure measures. It shows while the pointer rests on its trigger, or while the trigger has keyboard focus.
when to use
- To give the detail behind a short value or term: the time window behind “142 ms”, the full time behind “3 min ago”, what an abbreviation stands for.
- For information people can do without. Anything they need to finish a task belongs on the screen.
when to use something else
- To name a control that is only an icon. An icon button takes a label, which becomes its accessible name and the browser's own tooltip.
- For anything people need, such as an error, a warning or the consequence of an action. Touch screens have no hover, so people there never see the bubble. Put it on the page: in the hint or the error of a field, or in a banner.
- For more than a short phrase. The bubble doesn't wrap. Write it on the page, or in a modal when it needs a decision.
- For links, buttons or anything else to act on. The pointer can't rest on the bubble, the keyboard can't reach it, and it disappears when the pointer leaves the trigger. Offer the actions on the page, or behind an action menu.
anatomy
- Wrapper. It goes around your trigger. It is a tab stop of its own, and the bubble is its description.
- Trigger. Your content: a value or a term.
- Bubble. A small square box with a thin border, holding one line of small monospace text. It has a narrow maximum width. It sits a little away from the trigger, centred on it, with no arrow.
variants and states
The bubble goes above the trigger (the default), below it, or to its left or its right, centred along it. Nothing flips it or shifts it near the edge of the viewport, and a container that clips what overflows it cuts the bubble off: choose the side with room.
The bubble fades in quickly when the pointer rests on the trigger or the trigger gets keyboard focus, and it hides at once when both end. There is no delay before it shows.
Triggers can sit inside running text:
The rollout of api-7f9cbuilt 2026-06-15 09:12 finished on worker-23 of 3 replicas ready in 48 sfrom first pull to all replicas ready.
behaviour
- Resting the pointer on the wrapper shows the bubble, and moving the pointer away hides it. The bubble ignores the pointer, so it can't be hovered, selected or clicked.
- The wrapper is a tab stop: when tab brings keyboard focus to it, the bubble shows, and it hides when focus leaves.
The full keyboard map is on the tooltip's React page.
accessibility
- The bubble describes the wrapper, not your trigger: a screen reader reads it as the wrapper's description.
- The wrapper always adds a tab stop. On a value or a term, that is what lets people reach the detail with the keyboard. On a button, it means two tab stops for one control, and the button itself gets no description.
Today:
- The focused wrapper draws no focus outline. The bubble appearing is the only sign of focus.
- esc doesn't hide the bubble, and the pointer can't rest on it: two of the three things that WCAG 1.4.13 asks of content that appears on hover or focus. Keep only optional detail in it.
- Text longer than the bubble's maximum width doesn't wrap; it runs out of the bubble.
content
- One short phrase, lowercase, with no full stop: “p95 over the last 5 minutes”, “built 2026-06-15 09:12”.
- Add to the trigger; don't repeat it. A tooltip that says what the trigger already says is noise.
- Plain text only: no links, no buttons, nothing that has to be read in a particular order.
do and don't
related
- Icon button: names a control that is only an icon; its label is also the browser's own tooltip.
- Field: hints and errors next to the input they explain.
- Banner: information people need, on the page.
- Modal: when the detail needs a decision.
specs
| part | spec |
|---|---|
| Wrapper | A span around your children, with tabindex="0" and aria-describedby pointing at the bubble. outline: none, so it draws no focus ring. |
| Bubble | role="tooltip", its text from content. --surface-2 fill, a 1px --border-2 border, square, padding 4px 8px. JetBrains Mono 12px / 1.4 in --fg, on one line (white-space: nowrap), at most 240px wide. 6px from the trigger, centred on it, no arrow; z-index: 50, pointer-events: none. |
| Showing | While the wrapper is hovered or has keyboard focus (:focus-visible), the bubble fades in over 100ms ease, with no delay. It hides at once when both end. CSS only, with no script. |
| side | in code |
|---|---|
| Above (the default) | side="top" |
| Below | side="bottom" |
| Left | side="left" |
| Right | side="right" |
- Placement: with CSS alone (
position: absolute), so an ancestor withoverflow: hiddenclips the bubble, and nothing flips or shifts it at the viewport's edge. - Long text: past 240px, text runs out of the bubble instead of wrapping.
- Icon button: its
labelbecomes itsaria-labeland itstitle, the browser's own tooltip.