patternjavascriptModerate
Accessible tooltips and popovers — hover-triggered content that keyboard and AT users can reach
Viewed 0 times
WCAG 2.1 Level AA, ARIA 1.2
accessible tooltiptooltip keyboardaria-describedby tooltiprole tooltiphover content WCAG 1.4.13
Problem
Tooltips triggered only by mouseenter are inaccessible to keyboard users. Custom tooltip implementations hide content via CSS that screen readers cannot reach at all, or they expose tooltip text that is already in the button label, creating redundant announcements.
Solution
Build tooltips that are accessible on focus as well as hover, persist long enough to be read, and are dismissible with Escape.
// Tooltip pattern using aria-describedby
function Tooltip({ id, text, children }) {
const [visible, setVisible] = useState(false);
return (
<span
onMouseEnter={() => setVisible(true)}
onMouseLeave={() => setVisible(false)}
onFocus={() => setVisible(true)}
onBlur={() => setVisible(false)}
>
{React.cloneElement(children, {
'aria-describedby': id,
})}
<span
id={id}
role="tooltip"
hidden={!visible}
>
{text}
</span>
</span>
);
}
// Usage
<Tooltip id="tt-delete" text="Permanently remove this item">
<button>Delete</button>
</Tooltip>
// Tooltip pattern using aria-describedby
function Tooltip({ id, text, children }) {
const [visible, setVisible] = useState(false);
return (
<span
onMouseEnter={() => setVisible(true)}
onMouseLeave={() => setVisible(false)}
onFocus={() => setVisible(true)}
onBlur={() => setVisible(false)}
>
{React.cloneElement(children, {
'aria-describedby': id,
})}
<span
id={id}
role="tooltip"
hidden={!visible}
>
{text}
</span>
</span>
);
}
// Usage
<Tooltip id="tt-delete" text="Permanently remove this item">
<button>Delete</button>
</Tooltip>
Why
WCAG 1.4.13 requires hover-triggered content to be persistent, dismissible, and hoverable. Users with motor disabilities often move the mouse slowly and accidentally dismiss tooltips, and keyboard-only users never trigger mouseenter at all.
Gotchas
- role='tooltip' is only for supplementary descriptions — if the tooltip is the only label, use aria-label or a visible label instead
- Avoid putting interactive content (links, buttons) inside tooltips — use a popover with role='dialog' instead
- WCAG 1.4.13 requires the tooltip to remain visible when the user moves their cursor from the trigger to the tooltip
- Do not use title attribute as a tooltip — it is not keyboard accessible and is hidden on touchscreens
Context
Icon buttons with no visible label, supplementary UI hints, data visualization annotations
Revisions (0)
No revisions yet.