Patterns Tooltip

Tooltip

A short, supplementary description for an interactive control. Visible on hover and focus, dismissable, and never the only place an essential piece of information lives.

Reviewed against the methodology checklist Updated

Try the keyboard

When to use

A tooltip is supplementary information for a control. The user should be able to use the control without reading the tooltip — if they can’t, the tooltip’s content belongs in the visible label.

Use a tooltip when:

Don’t use a tooltip when:

The pattern

Accessible tooltip
Save for later

The minimum viable accessible tooltip is three things wired together:

  1. A focusable host element (a <button>, an input, a link).
  2. A tooltip element with role="tooltip" and an id.
  3. The host points at it via aria-describedby="<tooltip-id>".
HTML
<button type="button" aria-describedby="help-tip">
?
</button>

<span id="help-tip" role="tooltip">
We use this for the "what is this" hover.
</span>

aria-describedby makes the screen reader announce the tooltip text after the button’s name when focus arrives. CSS handles visibility on hover and focus:

CSS
.tooltip {
opacity: 0;
visibility: hidden;
transition: opacity 100ms;
}

button:hover .tooltip,
button:focus-visible .tooltip,
.tooltip-host:hover .tooltip,
.tooltip-host:focus-within .tooltip {
opacity: 1;
visibility: visible;
}

Note :focus-within on the wrapping host — it lets the tooltip stay visible while focus is anywhere inside (useful when the host is a small wrapper around an input).

Three rules from WCAG 1.4.13

  1. Hoverable — pointing at the tooltip itself must not dismiss it. (Otherwise users with low-precision pointers can’t read it.) Use a wrapping host so leaving the trigger doesn’t immediately hide the tooltip if the cursor moves toward the tip.
  2. Dismissable — Escape must close the tooltip without moving focus. Listen on the host:
JS
button.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
  button.blur();
  button.focus(); // keep focus, but lose hover
}
});
  1. Persistent — the tooltip stays until the user dismisses it, moves focus, or moves the pointer. Don’t auto-dismiss on a timeout.

When the icon is the label

For an icon-only button, the visible icon is decorative and the accessible name comes from aria-label. Tooltip then describes more, not the same thing:

HTML
<button type="button" aria-label="Bookmark" aria-describedby="bk-tip">

</button>
<span id="bk-tip" role="tooltip">Save for later</span>

Screen reader announces: “Bookmark, button. Save for later.” The label names what the button does (“bookmark”); the tooltip describes the consequence (“save for later”).

Anti-patterns

title attribute as a tooltip

HTML
<button type="button" title="Save for later">★</button>

Three problems:

  1. Not visible on focus — keyboard users only see it if they ALSO hover, which they probably can’t.
  2. Not dismissable — no way to close it without moving the pointer.
  3. Inconsistent SR support — some screen readers announce title, some don’t, some announce it only after the accessible name. Don’t rely on it for anything important.

The title attribute is fine as a fallback for mouse users when no real tooltip is implemented. It’s not a tooltip.

CSS-only tooltips on non-focusable elements

HTML
<span class="tooltip-host">
Hover me
<span class="tooltip">A definition</span>
</span>

If .tooltip-host isn’t focusable, keyboard users can’t reveal the tooltip. Either make the element focusable (and meaningful — usually a <button type="button"> or <a>), or put the information in the visible content.

Tooltip with a “click to close” inside

The moment the tooltip contains a link or a button, it’s not a tooltip — it’s a popover. Tooltips have pointer-events: none and can’t receive interaction. For “click to learn more” or “close button” you want a <dialog> or a custom popover with focus management. Mixing the two breaks the tooltip role contract.

Common variations

Checklist

Screen reader transcript

Voiced by George — reading what VoiceOver on macOS Safari announces. Other readers (NVDA, JAWS) phrase things differently; the meaning is what matters.

Screen reader announcements for each step
ListenYou doScreen reader says
Tab onto the question-mark buttonHelp, button. We use this for the "what is this" hover.
Press Escape(tooltip dismissed; focus stays on the button)

Screen reader transcript — anti-pattern

Same interaction, but on the broken version above. Notice what the user is missing.

Screen reader announcements for each step
ListenYou doScreen reader says
Tab onto the icon buttonBookmark icon, button. (no description — the user has to guess what bookmark means here)
Hover with a mouse("Save for later" appears visually — keyboard and screen-reader users never see it)

WCAG references