Designer view
Tooltip
A non-interactive floating panel that surfaces brief descriptive text about its trigger, revealed on hover or focus and dismissed on blur, pointer-leave, or Escape. Distinct from Popover (interactive) and Modal (blocking): tooltip content is read-only, the user never enters the tooltip, and Tab moves past the trigger without entering the tooltip body. Used for icon labels, abbreviation expansions, short helper text — never for essential information.
When to use
Use
When a control's accessible name benefits from a brief supplementary description that does not need to be permanently visible — icon-only buttons, abbreviations, status badges, column headers in dense tables. The description is non-essential (the control operates correctly without it) and short (one or two sentences).
Avoid
For interactive content (buttons, links, forms inside the floating surface) — that is `Popover`. For essential information that defines the control — that belongs in the accessible name (`aria-label`, visible text). For paragraph-length explanations — that is body prose or a Disclosure. For status messages that announce state changes — that is `Toast` or a `aria-live` region.
Versus related
- popover
`Popover` is interactive — the user may tab into the body and click controls. `Tooltip` is non-interactive — focus never enters the tooltip, and the body must contain only descriptive text. The boundary is canonical and APG- defined; do not blur it with "tooltip with a button" designs.
- modal
`Modal` is blocking and viewport-centred; `Tooltip` is non-blocking and trigger-anchored. They occupy opposite ends of the floating-surface spectrum (Modal: maximum modality, content-rich; Tooltip: zero modality, description-only).
- alert
`Alert` is an inline `aria-live` message announcing state changes; `Tooltip` is on-demand description revealed by hover or focus. Alert pushes content at the user; Tooltip is pulled by the user's interaction.
Figma anatomy
| Slot | Figma type | Hint |
|---|---|---|
trigger | instance | Consumer-provided trigger; in Figma the Tooltip frame is anchored to but does not contain the trigger |
container | frame | Floating frame with optional tip; max-inline-size cap |
arrow | rectangle | 6×6 triangle clipped from a rotated square |
Token usage per slot
container- spacing
- padding
spacing.tight
- padding
- radius
- corner
radius.sm
- corner
- color
- background
color.surface.sunken - foreground
color.text.primary - border
color.border.subtle
- background
- elevation
- shadow
elevation.md
- shadow
- typography
- size
text.sm - lineHeight
leading.snug
- size
arrow- color
- background
color.surface.sunken - border
color.border.subtle
- background
Figma ↔ Code property map
| Figma | Type | Code | Notes |
|---|---|---|---|
Variant | Variant | variant | Maps standard / inline-help. |
Side | Variant | side | top / right / bottom / left. Authored placement preference; auto-flips when colliding with viewport. |
Align | Variant | align | start / center / end along the perpendicular axis. |
Delay | Variant | delay | instant / short / base / long. Maps to library-specific milliseconds (Radix uses 0/200/700/1500 by default). |
Has Arrow | Boolean | arrow | Toggles the arrow slot. True by default for standard variant; false for inline-help. |
Content | Text | children | The tooltip text. Capped at one to two short sentences by canon. |
Motion
| Transition | Duration token |
|---|---|
open | motion.duration.fast |
close | motion.duration.instant |
Responsive behaviour
| Breakpoint | Change |
|---|---|
breakpoint.sm | At and below, hover-driven tooltips are unreliable because most touch devices do not surface persistent hover events. The canonical fallback is long-press (~500ms) plus focus on tap-and-hold; some implementations replace tooltip with a contextual Popover triggered on tap. Inline-help variant switches to tap-to-toggle automatically (the icon becomes a button with aria-haspopup). |
breakpoint.md | Above this width, hover and focus reveal as authored. The floating positioning honours `side` and `align`; auto-flip and shift work as designed. |
Internationalisation
RTL · mirroring
Side property is direction-neutral (top/right/bottom/left are physical). Align is logical (start/center/end follow inline direction). Tooltip text inherits document direction; mixed-direction text (Arabic content with English code snippets in the tooltip) follows the inner spans' `dir` attributes. Auto-flip is symmetric across directions. Arrow glyph is a triangle — direction-neutral.
Text expansion
Tooltip text expansion is constrained by the canonical max-inline-size (commonly 240–320px). Long-text languages (German, Russian, Finnish) may wrap to two or three lines; above three lines, the canonical mistake `tooltip-paragraph- length` applies — the content belongs in a Popover or body prose. Allow soft-wrap; never truncate with ellipsis (the truncated description loses meaning).
Variants, properties, states
Variants
Structurally different versions of the component.
standardinline-help Properties
The same component, parameterised.
| Property | Type |
|---|---|
side | top | right | bottom | left |
align | start | center | end |
delay | instant | short | base | long |
arrow | boolean |
States
Browser/user-driven (interactive) vs. app-driven (data).
| Kind | States |
|---|---|
interactive | hoverfocus-visible |
data | closedopeningopenclosing |
State transitions
| From | To | Trigger |
|---|---|---|
closed | opening | Pointer enters the trigger or focus moves to the trigger. The configured delay timer starts; the tooltip is not yet visually revealed. |
opening | open | The delay timer completes (or, under prefers-reduced-motion reduce, the tooltip appears immediately at the canonical instant fallback). aria-describedby is wired and the SR-announceable description is present. |
open | closing | Pointer leaves the trigger AND focus is not on the trigger; or focus leaves the trigger AND pointer is not over it; or the user presses Escape to dismiss persistently. The canonical OR-conjunction prevents flicker when the user moves focus while hovering. |
closing | closed | The exit animation completes (or immediately under reduced motion). aria-describedby remains on the trigger but the container is removed from the DOM (or hidden via CSS). |
Figma↔Code mismatches
- 01 Figma
A tooltip drawn with a button inside ("Got it" or "Learn more")
CodeA tooltip with role=tooltip, which APG forbids interactive content
ConsequenceDesigners may treat tooltip as a small popover and add dismiss buttons or links. Implementations that follow the Figma file create something with `role="tooltip"` plus a button — SR users cannot reach the button (focus does not enter tooltips), and the visual affordance suggests interaction that does not work. The pattern collapses to Popover.
CorrectTreat the boundary as canonical: tooltips contain only non-interactive content. If the surface needs a dismiss button or any interaction, redesign as a Popover. Document this distinction prominently in the canonical reference and in `whenToUse`.
- 02 Figma
A tooltip drawn that only appears on hover
CodeA tooltip that fires on hover but not on focus
ConsequenceDesigners think tooltips are a hover affordance; developers shipping hover-only break keyboard accessibility — keyboard users tab to the trigger but never see the description. Touch devices fall back to long-press; without focus-trigger they have no canonical reveal at all on touch keyboards.
CorrectTooltip MUST trigger on both pointer-enter and focus. Document this as a non-negotiable a11y rule in the canonical reference. Touch devices use long-press by convention; the reveal happens via the same focus event pattern when the trigger is focusable.
- 03 Figma
Tooltip drawn carrying essential information
CodeTooltip used as the primary descriptor for a control
ConsequenceDesigners may use tooltip text as the only descriptor for icon-only controls; developers shipping this lose the description for SR users (who hear the icon's `aria-label` only) and for users on slow connections (who may not see the tooltip render before activating). The essential information is locked behind a hover-only reveal.
CorrectEssential information is in the trigger's accessible name (`aria-label`, visible text, etc.), never in the tooltip. The tooltip supplements with details that are useful but not required to operate the control. Document the canonical contract: tooltip is supplementary description, not primary label.
- 04 Figma
A tooltip drawn pointing at a passive area (a label, a static word)
CodeA tooltip wired to a non-focusable element with `aria-describedby`
ConsequenceDesigners anchor tooltips to non-interactive surfaces; developers wire them with no focus-trigger because the element is not focusable. Keyboard users miss the tooltip entirely; pointer users see it on hover but the experience is incomplete.
CorrectIf the element needs a tooltip, it must be focusable — either it's already an interactive control, or wrap it in a focusable element (a button, a `<dfn>` or `<abbr>` with `tabindex="0"`). The tooltip's reveal contract requires both pointer and keyboard reachability.
Common mistakes
#tooltip-with-interactive-content
Tooltip containing buttons or links
The tooltip body has interactive elements (a "Learn more" link, a "Got it" button). APG forbids this — tooltip content is non-interactive, focus does not enter the tooltip, and SR users cannot reach the interactive children.
If the surface needs interaction, use a Popover. The distinction is canonical: Tooltip = non-interactive descriptive text; Popover = interactive contextual content. No "tooltip with a button" middle ground.
#tooltip-no-focus-trigger
Tooltip only reveals on pointer hover
The tooltip fires on `mouseenter` but not on `focus` events. Keyboard users tab to the trigger; the tooltip never appears. Touch devices that surface the focus event also lose the tooltip.
Always pair pointer-enter with focus-enter as reveal triggers, and pointer-leave with focus-leave as dismiss triggers (with the OR-conjunction documented in the transitions block — tooltip stays open while either pointer or focus remains).
#tooltip-essential-info
Essential information in tooltip
The tooltip text is the only descriptor for an icon-only control. SR users who skip the tooltip via verbose-mode-off get just the icon's `aria-label`; pointer users on slow connections may activate before the tooltip renders.
Essential info goes in the trigger's accessible name. The tooltip supplements with non-essential details. The test: remove the tooltip — does the control still operate correctly with full meaning? If no, the tooltip held essential info that belongs in the label.
#tooltip-paragraph-length
Tooltip with multi-paragraph content
The tooltip body grows beyond a brief sentence — multiple sentences, lists, or paragraph-length prose. Hover dismisses while the user is reading; SR description fires the entire paragraph as one announcement.
Cap tooltip content at one or two short sentences. Anything longer should live in body prose, a Popover, or a Disclosure. Document the canonical length convention and enforce it in design review.
#tooltip-pointer-events-block
Tooltip captures pointer events from underlying content
The tooltip's container has `pointer-events: auto` — the user's mouse enters the tooltip area on its way to another control, the tooltip blocks the click, and the trigger cannot be reached again. Or worse, the tooltip itself becomes hoverable and traps the cursor in a hover loop.
Tooltip container has `pointer-events: none` by default. The tooltip is a non-interactive overlay that text can be read from but not clicked into. Pointer events pass through to the underlying content. The trigger handles all hover logic; the container is purely visual.
Accessibility hints
| Slot | Accessibility hint | |
|---|---|---|
trigger | Trigger element carries `aria-describedby` referencing the tooltip container's id (NOT `aria-labelledby` — the tooltip is supplementary description, not the accessible name). For triggers without their own accessible name (e.g. an IconButton), the tooltip text duplicates as the `aria-label` plus the `aria-describedby` reference, so SR users hear the label first then the description. | |
container | Apply `role="tooltip"`. The container has no interactive children — buttons, links, form controls inside a tooltip violate the APG contract. Sized to text; do not host layouts. The container does NOT receive focus; tabindex is absent. | |
arrow | Decorative; do not put `role` on it. The trigger-to-tooltip relationship is communicated by `aria-describedby`, not by the arrow. |