Tooltips look trivial, but they are one of the most consistently broken components on the web. The pattern that ships with most UI libraries shows content only on mouse hover, vanishes the moment the pointer drifts, and is invisible to keyboard and screen reader users entirely. That combination can fail more than one WCAG success criterion at once.
This guide shows web developers how to build accessible tooltips that satisfy WCAG 2.2 Success Criterion 1.4.13 (Content on Hover or Focus) and pair correctly with screen readers. We will cover the three behavioral requirements (dismissible, hoverable, persistent), when to use aria-describedby versus aria-labelledby, and why hover-only information is a trap you should design out rather than patch.
What WCAG 1.4.13 actually requires
1.4.13 Content on Hover or Focus is a Level AA criterion in WCAG 2.1 and 2.2. It applies whenever additional content appears on pointer hover or keyboard focus and then disappears — tooltips, popovers, hover cards, and disclosure widgets all qualify. The European Accessibility Act (Directive (EU) 2019/882), which applies from 28 June 2025, uses WCAG 2.2 Level A and AA via EN 301 549 as its baseline, so 1.4.13 is in scope for products covered by the EAA.
The criterion sets three conditions that must all hold, unless the visual presentation of the extra content is controlled by the user agent and not modified by the author (for example, a native title tooltip):
- Dismissible: the user can close the extra content without moving the pointer or focus — typically by pressing Escape — while keeping the trigger hovered or focused.
- Hoverable: if the content appears on hover, the user can move the pointer onto the tooltip itself without it disappearing. This is what kills the common "gap between trigger and tooltip" bug.
- Persistent: the content stays visible until the user dismisses it, moves hover/focus away, or it is no longer valid. It must not auto-hide on a timer.
Most off-the-shelf tooltips fail "hoverable" because there is a dead zone between the trigger and the bubble, and fail "dismissible" because they have no Escape handler. Both are fixable without redesigning the component.
aria-describedby vs aria-labelledby: pick deliberately
The accessible name and the accessible description are not interchangeable, and choosing the wrong one changes what a screen reader announces. Use aria-describedby when the tooltip provides supplementary, non-essential detail about an element that already has a name. A help icon next to a password field is a classic case:
<input id="pw" type="password" aria-describedby="pw-tip"> with <span id="pw-tip" role="tooltip">At least 12 characters, one number.</span>. A screen reader reads the field's label first, then the description after a pause.
When to use aria-labelledby instead
If the tooltip is the only text label for a control — an icon-only button with no visible text — the tooltip text is the name, not a description. Use aria-labelledby (or a plain aria-label) so the control is announced at all. A trash-can button with aria-labelledby pointing at a "Delete" tooltip is named "Delete, button"; the same button with aria-describedby would announce as "button" with a trailing description, which is far weaker. As a rule: describedby augments an existing name, labelledby supplies one.
Put role="tooltip" on the bubble, and make sure the element referenced by aria-describedby is present in the DOM (even if visually hidden) at the time focus lands — references to absent or display:none-removed nodes are silently dropped by assistive tech. See our ARIA best practices guide for more on name and description computation.
Why the title attribute is not enough
It is tempting to reach for the native title attribute and call it done. Don't. title has well-documented accessibility problems: it never appears on keyboard focus (only on mouse hover), it is not reliably exposed by all screen readers, it cannot be styled, it auto-hides after a few seconds (failing persistent), and it cannot be dismissed with Escape (failing dismissible). It also does nothing for touch users, who have no hover state at all.
title is acceptable only for genuinely redundant text — for example, mirroring a visible link's text — where losing it costs nothing. For any real information, build a managed tooltip with the keyboard and pointer behaviors below.
A correct implementation pattern
Here is the behavioral contract a compliant tooltip needs, independent of framework:
- Trigger is a real focusable element (a <button>, or the input it describes) — never a bare <div> or <span> with no tabindex.
- Show the tooltip on both mouseenter and focus; hide on both mouseleave and blur. Binding only to mouse events excludes keyboard users and fails 1.4.13's focus path.
- Add a keydown listener for Escape that hides the tooltip while leaving focus on the trigger (dismissible).
- Eliminate the dead zone: either render the tooltip touching the trigger, or keep it visible while the pointer is over either the trigger or the bubble, so the user can move onto it (hoverable).
- Never start a setTimeout that auto-hides visible content (persistent). A short show-delay on hover is fine; an auto-hide is not.
If you build in React, Vue, or another component framework, prefer a vetted primitive — such as Radix UI, React Aria, or the Floating UI tooltip recipe — over hand-rolling, because they already handle the dead zone, Escape, and focus/blur edges. Whatever you use, test it: tab to the trigger, confirm the tooltip appears, press Escape, and confirm it closes without focus jumping.
Avoid putting essential information in a tooltip at all
The most robust fix is often to not rely on hover. Tooltips are poor carriers for anything a user must read to complete a task. They are invisible until interacted with, awkward on touch screens, easy to miss, and prone to clipping inside scroll containers. Reserve them for secondary, glance-able hints.
If the content is required — a form field's format rules, an error explanation, the meaning of a status badge — render it as persistent visible text instead. Inline hint text under a field, an always-visible legend, or an expandable disclosure all serve users better and sidestep 1.4.13 entirely. This matters most in forms, where hidden constraints cause real errors; our accessible forms guide covers hint and error patterns in detail. When you do need a tooltip, you can sanity-check the whole component against an accessibility checklist or run a free page scan with AccessScan to catch missing names, descriptions, and focus issues before they ship.