Others
Tooltip
AvailableSupplementary text that appears on focus or hover. Uses aria-describedby with keyboard support.
Specification updated
- Enhancement#5175(opens in a new tab)1.4.13: Rewrite the "Dismissible" explanation and clarify the two exemptions
Specification changes under review
- #3431(opens in a new tab)Tooltip: role="tooltip" to become name-prohibited
What Is a Tooltip?
A tooltip is a small popup that appears when an element receives focus or hover, providing supplementary description for that element. They are commonly used to explain icon buttons or add hints to input fields.
An important caveat: tooltips are "supplementary," not primary information. Essential information required for an operation should never be placed only in a tooltip.
Why Does Accessibility Matter?
Custom tooltips or relying on the title attribute cause the following problems:
- Not shown to keyboard users. If the tooltip only appears on hover (mouse), users who navigate with Tab won't be able to read the description.
- Not shown to touch users. Smartphones and tablets don't have hover.
- The
titleattribute is unreliable. It displays slowly, can't be styled, and assistive technology support is inconsistent. - Can't be dismissed. WCAG requires that supplementary popups can be dismissed with Esc.
The solution is to associate the tooltip with aria-describedby, show it on both focus and hover, and allow dismissal with Esc.
Live Demo (Recommended Implementation)
The button below has an APG-compliant tooltip. Verify that it appears not only on mouse hover but also when focused with Tab, and can be dismissed with Esc.
Try it: Tab to focus the button and the tooltip appears → Esc to dismiss → Hover with mouse and it appears again. The tooltip itself does not receive focus.
Tip
When a screen reader focuses the button, it announces something like "Save, button, Saves your changes to the server" — the name followed by the description. This is the effect of aria-describedby.
Keyboard Interaction
| Key | Action | Priority |
|---|---|---|
| Tab | Focus on the trigger displays the tooltip | Required |
| Esc | Dismiss the visible tooltip (focus remains on the trigger) | Required |
Note
The tooltip itself does not receive focus (it is not a Tab target). Since it's merely supplementary to the trigger, avoid placing links or buttons inside it (if you need interactive content, use a popover or dialog instead).
Required WAI-ARIA Roles & Properties
| Target | Attribute / Role | Meaning |
|---|---|---|
| Trigger | aria-describedby="<tooltip id>" | Associates the tooltip as a description. Read aloud when the trigger receives focus. |
| Tooltip element | role="tooltip" | Identifies the element as a tooltip. |
| Tooltip element | hidden (when not visible) | Excludes it from screen readers and rendering while hidden. |
| Trigger | Focusable element (e.g. <button>) | Ensures keyboard accessibility. Do not use <div> or similar. |
Recommended Pattern (Good)
Good / Recommended
Associate with aria-describedby, show on both focus and hover, and dismiss with Esc.
Markup:
<!-- The trigger must be a focusable element (e.g. button) -->
<button type="button"
id="tip-trigger"
aria-describedby="tip-text">
Save
</button>
<!-- The tooltip itself. Not focusable, role="tooltip" -->
<div id="tip-text" role="tooltip" hidden>
Saves your changes to the server
</div>Show/hide script (supporting both focus and hover + Esc is the key):
const trigger = document.getElementById('tip-trigger');
const tip = document.getElementById('tip-text');
if (trigger && tip) {
const show = () => { tip.hidden = false; };
const hide = () => { tip.hidden = true; };
// Must show on both focus and hover
trigger.addEventListener('focus', show);
trigger.addEventListener('blur', hide);
trigger.addEventListener('mouseenter', show);
trigger.addEventListener('mouseleave', hide);
// Allow dismissing with Esc
trigger.addEventListener('keydown', (e) => {
if (e.key === 'Escape') hide();
});
}Note
As a note, tooltip content should be kept to "nice-to-have" supplementary information.Information essential for operation should be written directly in the label or body text. The displayed tooltip should be dismissible with Esc and should not disappear when the mouse moves over it (WCAG 1.4.13).
Anti-Pattern (Bad)
The buttons below rely on the title attribute only, or use a hover-only custom tooltip. Focusing with Tab shows no description, and Esc doesn't dismiss anything.
Try it: Tab to focus shows no description (left: title doesn't appear on keyboard focus / right: hover-only). Neither works on touch devices, and Esc has no effect.
<!-- ❌ Anti-pattern 1: title attribute only -->
<button type="button" title="Saves your changes">Save</button>
<!-- ❌ Anti-pattern 2: hover-only custom tooltip -->
<span class="has-tip">Save
<span class="tip">Saves your changes</span>
</span>Bad / Avoid
Problems with this implementation:
- Doesn't appear on keyboard focus — Neither
titlenor hover-only CSS shows on Tab focus. - Doesn't appear on touch — On devices without hover, the tooltip never displays.
- No association — Without
aria-describedby, the description and trigger are not linked. - Can't be dismissed — No mechanism to close with Esc (WCAG 1.4.13 violation).
Tip
The title attribute is "better than nothing," but it displays slowly, can't be styled, and doesn't appear on keyboard focus. If you want to properly show supplementary descriptions, implement them with aria-describedby + custom show/hide logic.
Implementation Checklist
- The trigger is a focusable element (e.g. <button>)
- The trigger has aria-describedby linking to the tooltip
- The tooltip element has role="tooltip"
- The tooltip appears on both focus and hover
- Esc dismisses the tooltip (focus remains on the trigger)
- The tooltip itself does not receive focus and contains no interactive elements
- Information essential for operation is not placed only in the tooltip
Field Notes — Where practice diverges from the APG
Treating tooltips as visual-only hints — no role="tooltip", no aria-describedby
- The APG says
- The tooltip element should have role="tooltip" and be referenced from the trigger via aria-describedby, appearing on both hover and focus and dismissible with Escape. Touch input is not addressed at all.
- What practice does
- Base UI's Tooltip sets neither role="tooltip" nor aria-describedby; its docs state tooltips are visual aids and essential information belongs directly on the trigger (e.g. aria-label). Tooltips are disabled on touch devices, with Popover (openOnHover) recommended for essential content. Material UI guards against accidental touch triggering with enterTouchDelay (700ms long-press by default).
- Why
- Tooltips are designed around hover and focus — neither exists on touch devices, where a tap activates immediately. Information that lives only in a tooltip is structurally unreachable for some users. Sarah Higley flagged this incompatibility in 2019 and the spec side has not resolved it; Radix, which follows the APG faithfully, keeps receiving "tooltip does not open on touch" issues. Base UI codified a decision rule — is the trigger's purpose the open/close itself? — and confines tooltips to redundant visual hints.
Adopted by:Base UI、Material UI
Sources:Base UI — Tooltip(Accessibility guidelines)(opens in a new tab)、Sarah Higley — Tooltips in the time of WCAG 2.1(opens in a new tab)、MUI — Tooltip(opens in a new tab)
Ariakit defaults to aria-labelledby instead of aria-describedby
- The APG says
- The trigger references the tooltip via aria-describedby — the tooltip is supplementary description, not the source of the accessible name.
- What practice does
- Ariakit v2 changed the default association to aria-labelledby. Consumers opt in to aria-describedby only when the tooltip is genuinely a description.
- Why
- In the wild, tooltips overwhelmingly label icon-only buttons that have no other accessible name. With aria-describedby such a button has no name at all; the maintainer verified inconsistent announcements across screen readers (VoiceOver duplicating "Undo, button, Undo" versus NVDA). aria-labelledby announces consistently, so Ariakit chose real-world usage over the APG default — while openly acknowledging the divergence and the remaining debate about dropping role="tooltip" entirely.
Adopted by:Ariakit
Sources:Ariakit — Tooltip(opens in a new tab)、ariakit/ariakit#2228 — Tooltip accessibility(opens in a new tab)
The APG tooltip pattern itself lacks consensus — the field reaches for toggletips
- The APG says
- The APG tooltip pattern is explicitly marked "work in progress" without task force consensus — unresolved since 2016. The click-toggled "toggletip" does not exist in the APG pattern list at all.
- What practice does
- Sarah Higley calls role="tooltip" the "unloved child of roles" and recommends tooltips only for non-essential supplements. Heydon Pickering proposes the toggletip — a click-toggled info box that works across touch, mouse, and keyboard — as the replacement.
- Why
- Beyond the structural hover/focus flaw, WCAG 2.1's 1.4.13 (Content on Hover or Focus) added dismissable / hoverable / persistent requirements that sharply raised the cost of an APG-style tooltip. The W3C ARIA WG itself has acknowledged in issues and TPAC 2023 discussions that the current tooltip role cannot express rich-content use cases and needs rework. Toggletips draw their own criticism — essential information should not hide behind a tiny button — so they are a leading alternative, not a settled consensus.
Adopted by:Sarah Higley、Heydon Pickering (Inclusive Components)
Sources:Sarah Higley — Tooltips in the time of WCAG 2.1(opens in a new tab)、Inclusive Components — Tooltips & Toggletips(opens in a new tab)、w3c/aria-practices#128 — Tooltip pattern consensus(opens in a new tab)
Source (English):Tooltip Pattern — W3C APG(opens in a new tab)