Short supplementary text naming or clarifying its trigger on hover or keyboard focus — a floating label, never a surface for content; anything clickable inside one belongs in a Menu or Dialog, a component boundary, not a styling choice. The pill is the inverted pair (text.primary under text.inverse — Badge's --inverted recipe) at font.size.caption, with radius.control, space.1-5 block padding, space.2 inline padding, and shadow.low. It deliberately does not borrow the popover panel recipe and rides z.tooltip above the modal layer so tooltips keep working inside dialogs.
One part: the pill. Width rides the content (max-content) up to component.tooltip.max-width — the second component token after scrollbar radius, 240px in the base — then wraps at font.line.normal. The cap sizes the box but never shrinks it back, so a wrapped pill keeps the full cap and pads the short side; L1 spends that space on text-wrap: pretty rather than on a stranded last word, and L2 sets the width to the widest rendered line once it has measured. A tooltip that fits one line has neither problem, which is what the phrase-not-paragraph rule is protecting.
Width rides the content to component.tooltip.max-width, then wraps. The second pill is a Don't, kept because the failure is worth seeing: past the cap the box stays at its full width and pads the short side, since max-width caps a box but cannot shrink it back once the text has wrapped. The wrap is a safety net, not a layout — copy this long wants to be a visible label or FormField help text. The validator holds new tooltips to a one-line budget; this one is exempt by data-demonstrates-wrap so it can go on demonstrating what it warns about.
The side+align grammar, contracted as axes: side positions the pill against the trigger wrapper (the positioned ancestor) at one space.1 offset, and align swaps the centered cross axis for the anchor's start or end edge — one modifier each, twelve combinations, top-center the default since a tooltip must not cover the control's own content. On left and right the cross axis is vertical, so start means the anchor's top edge; the side is the author's deliberate call, and flipping on viewport collision is L2 measurement that never changes the alignment.
All twelve side × align combinations, arranged as they place — every trigger sits where its tooltip goes. Hover or focus any of them; flipping on collision is L2 and never changes alignment.
Visibility is host behavior under the contract: show on hover and on :focus-visible alike after a short warm-up, hide immediately on leave and on Escape without moving focus. There is no arrow, so the pill stays placement-agnostic and flipping costs nothing.
The canonical consumer — an icon-only toolbar: each control keeps its aria-label, and the tooltip repeats that name through aria-describedby. Tab through to see focus behave exactly like hover.
A tooltip is a label, not a place.
| Verdict | Rule |
|---|---|
| Do | Wire the trigger's aria-describedby to the tooltip's id; the tooltip carries role="tooltip". |
| Do | Keep the icon-only control's aria-label — the tooltip repeats that name as a description; it never replaces the accessible name. |
| Do | Show it on keyboard focus exactly as on hover, and dismiss on Escape without moving focus — a hover-only tooltip excludes keyboard users. |
| Do | Keep the label a phrase — sentence case, no markup; copy that regularly wraps belongs in a visible label or FormField help text. |
| Don't | Let the label reach the wrap cap. A wrapped pill keeps the full cap and pads the short side, because max-width cannot shrink a box back; the width is L2's to set once measured. A phrase that fits one line has neither problem. |
| Don’t | Put essential information or interactive content in a tooltip — if it must be clicked or must be read, it is a Menu, Dialog, or visible text. |
| Don’t | Tooltip the obvious — a control with a visible label needs no tooltip repeating it. |