Canonical source:
examples/TOOLTIP_ACCESSIBILITY_BEST_PRACTICES.mdThis skill is derived from that file. When in doubt, the example is authoritative.
Apply these rules when implementing or reviewing tooltip components.
Tooltips must convey supplementary information to all users regardless of input method. A tooltip unreachable by keyboard is an accessibility barrier.
Tooltips are non-interactive supplementary labels only. If the content is essential, or contains links/buttons, use persistent text or a popover instead.
<button type="button" aria-describedby="save-tooltip">Save</button>
<div id="save-tooltip" role="tooltip" hidden>
Save changes to your draft
</div>Rules:
- Tooltip element must have
role="tooltip" - Trigger must reference tooltip via
aria-describedby - Use
hiddento conceal; neveraria-hidden="true"on an active tooltip - Each tooltip must have a unique
id - Do not put
role="tooltip"on the trigger
For icon-only triggers, add aria-label for the accessible name:
<button type="button" aria-label="Delete item" aria-describedby="delete-tip">
<!-- SVG icon -->
</button>
<div id="delete-tip" role="tooltip" hidden>
Permanently removes this item from your account
</div>Tooltips must appear on both hover and keyboard focus:
function showTooltip() { tooltip.removeAttribute('hidden'); }
function hideTooltip() { tooltip.setAttribute('hidden', ''); }
trigger.addEventListener('mouseenter', showTooltip);
trigger.addEventListener('mouseleave', hideTooltip);
trigger.addEventListener('focusin', showTooltip);
trigger.addEventListener('focusout', hideTooltip);
// Escape dismisses without moving focus
trigger.addEventListener('keydown', (e) => {
if (e.key === 'Escape') hideTooltip();
});| Requirement | Description |
|---|---|
| Dismissible | Escape dismisses without moving pointer/focus |
| Hoverable | User can move pointer over tooltip without it disappearing |
| Persistent | Stays visible until trigger loses focus/hover |
| Key | Behavior |
|---|---|
Tab / Shift+Tab |
Focus moves to/from trigger; tooltip shows/hides |
Escape |
Dismisses tooltip; focus stays on trigger |
Tooltips must NOT be focusable themselves (no tabindex on tooltip container).
- Tooltip text: 4.5:1 contrast against tooltip background
- Tooltip background: 3:1 contrast against adjacent surfaces
- Respect
prefers-reduced-motion:
@media (prefers-reduced-motion: no-preference) {
[role="tooltip"] { transition: opacity 0.15s ease; }
}Hover tooltips don't work on touch screens. Use toggletip pattern:
<button type="button" aria-expanded="false" aria-controls="info-tip"
aria-label="More information about password requirements">
<span aria-hidden="true">ⓘ</span>
</button>
<div id="info-tip" hidden>
Your password must be at least 12 characters and include a number.
</div>Toggle aria-expanded and hidden together on click.
- Label of control is self-explanatory
- Information must be readable at all times
- Touch-only interface (use toggletip or persistent help text instead)
- Content is long enough for a popover
- Content contains interactive elements (links, buttons)
-
role="tooltip"on tooltip element - Trigger has
aria-describedbypointing to tooltip - Tooltip shows on both hover and keyboard focus
-
Escapedismisses without moving focus - Tooltip cannot be hovered away accidentally (hoverable)
- Tooltip not focusable itself
- Text contrast 4.5:1; background 3:1 against surface
- Touch/mobile has toggletip fallback
-
prefers-reduced-motionrespected for animations
- 1.4.3 Contrast Minimum (AA)
- 1.4.13 Content on Hover or Focus (AA)
- 2.1.1 Keyboard (A)
- 4.1.2 Name, Role, Value (A)