← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

Accessible Tooltips

Accessibility Intermediate
debt(d4/e3/b3/t7)
d4 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches' (d5), axe/lighthouse/wave can flag title-only tooltips and missing aria-describedby patterns, but many tooltip failures (hover-only, dismiss-on-move) require manual keyboard testing, so slightly better than pure silent-in-prod: d4.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3), the quick_fix is to swap title for a role='tooltip' + aria-describedby pattern with focus/escape handlers — a small refactor per tooltip component rather than a one-liner.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3), tooltips are a UI-layer concern affecting the component library; once a reusable accessible tooltip exists, the rest of the codebase is unaffected.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7), the misconception is that title= gives everyone a tooltip, but it silently fails for keyboard, touch, and many screen readers — the 'obvious' HTML-native approach contradicts accessibility requirements.

About DEBT scoring →

Also Known As

aria tooltip accessible hint wcag tooltip pattern

TL;DR

Tooltips that appear on hover, focus, and keyboard interaction, are associated with their trigger via aria-describedby, and can be dismissed without moving the pointer.

Explanation

A tooltip is a short piece of supplementary text that describes or clarifies a control. Making one accessible is harder than it looks, because the default browser 'title' attribute is inconsistently exposed to assistive technology, disappears too quickly, and is completely inaccessible to keyboard-only and touch users. WCAG 2.1 SC 1.4.13 (Content on Hover or Focus, Level AA) sets three concrete requirements: the tooltip must be dismissible without moving the pointer (typically via the Escape key), it must be hoverable so a user can move the pointer onto the tooltip content itself without it vanishing, and it must be persistent until the user dismisses it or the trigger loses focus. The tooltip must also appear on keyboard focus, not just mouse hover, otherwise keyboard users never see it. The correct association pattern is an element with role='tooltip' referenced from the trigger via aria-describedby. Do not use aria-labelledby for tooltips - the trigger should already have its own accessible name, and the tooltip supplements that name with a description. Never put interactive content (links, buttons, form fields) inside a tooltip; if you need that, you actually want a popover or a disclosure widget with different semantics. Tooltips should not carry essential information that is unavailable elsewhere, because touch users and users of some assistive technologies may not trigger them. Watch out for tooltips on disabled buttons: disabled elements do not receive focus, so wrap the button in a focusable span or use aria-disabled instead of the disabled attribute. Test with a screen reader (NVDA, VoiceOver), by tabbing through the page, and by pressing Escape while a tooltip is open.

Common Misconception

Adding a title attribute to an element gives you a tooltip that works for everyone. In reality, title is unreliable across screen readers, invisible on touch devices, and cannot be triggered by keyboard focus, so it fails WCAG requirements.

Why It Matters

Tooltips often carry information users need to complete a task, and inaccessible implementations exclude keyboard users, screen reader users, and touch users while creating a Level AA WCAG failure.

Common Mistakes

  • Relying on the title attribute, which is not exposed on focus and is unreliable across assistive technologies.
  • Showing the tooltip on hover only, so keyboard users tabbing to the trigger never see it.
  • Making the tooltip vanish as soon as the pointer moves, preventing users from reading long content or interacting with it.
  • Placing interactive elements like links or buttons inside a tooltip, which requires a popover pattern instead.
  • Attaching tooltips to disabled buttons, which cannot receive focus and therefore cannot trigger the tooltip via keyboard.

Avoid When

  • Do not use a tooltip when the content is essential; put it inline in the UI where every user can perceive it.
  • Do not use a tooltip pattern when the content contains interactive elements - use a popover or disclosure widget instead.
  • Do not attach tooltips to genuinely disabled buttons; use aria-disabled on a focusable element so the tooltip can be reached.

When To Use

  • To supplement an already-labelled control with a short, non-essential hint or clarification.
  • For icon-only buttons where the icon has an accessible name but users benefit from an expanded description.
  • To explain constraints or formats for form inputs where the label alone is insufficient.

Code Examples

✗ Vulnerable
<!-- Fails WCAG: title only, no keyboard, no dismiss, no persistence -->
<button title="Deletes this record permanently">
  Delete
</button>

<!-- Also fails: hover-only, no ARIA association, vanishes on pointer move -->
<span class="info-icon"
      onmouseover="showTip()"
      onmouseout="hideTip()">?</span>
<div class="tip" id="tip" hidden>Only admins can undo this.</div>
✓ Fixed
<!-- Accessible tooltip: focusable trigger, aria-describedby, hoverable, Escape to dismiss -->
<button aria-describedby="delete-tip" id="delete-btn">
  Delete
</button>
<div role="tooltip" id="delete-tip" hidden>
  Deletes this record permanently.
</div>

<script>
  const btn = document.getElementById('delete-btn');
  const tip = document.getElementById('delete-tip');
  let hideTimer;

  const show = () => { clearTimeout(hideTimer); tip.hidden = false; };
  // Small delay so the user can move the pointer onto the tooltip (SC 1.4.13 hoverable)
  const hide = () => { hideTimer = setTimeout(() => tip.hidden = true, 150); };

  // Show on both hover AND focus (SC 1.4.13)
  btn.addEventListener('mouseenter', show);
  btn.addEventListener('focus', show);
  btn.addEventListener('mouseleave', hide);
  btn.addEventListener('blur', hide);

  // Keep visible while pointer is on the tooltip itself
  tip.addEventListener('mouseenter', show);
  tip.addEventListener('mouseleave', hide);

  // Dismissible with Escape without moving pointer (SC 1.4.13)
  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape' && !tip.hidden) {
      clearTimeout(hideTimer);
      tip.hidden = true;
      btn.focus();
    }
  });
</script>

Added 21 Jul 2026
Views 78
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 2 pings T 1 ping W 4 pings T 4 pings F 4 pings S 0 pings S 1 ping M 1 ping T 0 pings W 2 pings T 1 ping F 2 pings S 2 pings S 0 pings M 2 pings T 0 pings W 5 pings T 0 pings F
No pings yet today
ChatGPT 2 Bing 2 Meta AI 1
Bing 10 ChatGPT 4 Google 4 Applebot 3 SEMrush 2 Perplexity 2 Meta AI 2 PetalBot 1 Unknown AI 1 Ahrefs 1 Amazonbot 1
crawler 31
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Medium
⚡ Quick Fix
Replace title attributes with a role='tooltip' element referenced by aria-describedby, show it on focus as well as hover, and make Escape dismiss it.
📦 Applies To
html web browser
🔗 Prerequisites
🔍 Detection Hints
title="[^"]+" on interactive elements, or tooltip components without aria-describedby / role="tooltip" / focus handlers
Auto-detectable: ✓ Yes axe lighthouse wave
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Medium Context: Function Tests: Update


✓ schema.org compliant