Overlays
Tooltip
A supplementary popup showing extra info on hover or focus.
Last verified against WCAG 2.2 and WAI-ARIA APG 1.2 on 2026-07-02.
Implementation
Shows on hover AND focus, role="tooltip" with aria-describedby, Escape to dismiss, and a brief hover-delay so the pointer can reach the tooltip itself.
tooltip-pattern.tsx
function TooltipPattern({ label, tooltip }) {
const [visible, setVisible] = useState(false);
const hideTimeoutRef = useRef(null);
const tooltipId = useId();
function show() {
clearTimeout(hideTimeoutRef.current);
setVisible(true);
}
// Delay gives the pointer time to move from the trigger onto the
// tooltip itself (SC 1.4.13 "hoverable").
function scheduleHide() {
hideTimeoutRef.current = setTimeout(() => setVisible(false), 150);
}
return (
<span style={{ position: "relative" }}>
<button
aria-describedby={visible ? tooltipId : undefined}
onMouseEnter={show}
onMouseLeave={scheduleHide}
onFocus={show} // <- keyboard users MUST get this too
onBlur={() => setVisible(false)}
onKeyDown={e => { if (e.key === "Escape") setVisible(false); }}
>
{label}
</button>
{visible && (
<span id={tooltipId} role="tooltip" onMouseEnter={show} onMouseLeave={scheduleHide}>
{tooltip}
</span>
)}
</span>
);
}Live demo
Required roles, states & properties
| Element | Attribute | Why |
|---|---|---|
| Tooltip popup | role="tooltip" | Identifies the popup as supplementary description text, not an interactive widget or landmark. |
| Trigger element | aria-describedby | Programmatically links the trigger to the tooltip's id so a screen reader announces the tooltip text as a description whenever the trigger is focused — this is the whole mechanism; without it the tooltip is invisible to AT even if it's visible on screen. |
| Tooltip popup content | Text only, no interactive children | A tooltip must not contain links, buttons, or form controls. Content that needs to be interactive belongs in a Popover-style pattern instead. |
Keyboard interaction model
| Key | Behavior |
|---|---|
| Tab (to the trigger) | Shows the tooltip — focus alone must trigger it, not just mouse hover. |
| Shift+Tab / Tab (away) | Hides the tooltip as focus leaves the trigger. |
| Escape (while trigger has focus) | Hides the tooltip without moving focus away from the trigger (dismissible, per SC 1.4.13). |
Focus management rules
- The tooltip never receives focus itself — it is purely supplementary to whatever element is already focused.
- Focusing the trigger shows the tooltip; blurring the trigger hides it.
- Escape hides the tooltip while leaving focus exactly where it was, on the trigger.
- A short close delay on mouseleave lets the pointer travel from the trigger onto the tooltip content itself without it disappearing mid-move.
WCAG 2.2 success criteria mapping
| SC | Name | Level | Why it applies |
|---|---|---|---|
| 1.4.13 | Content on Hover or Focus | AA | Content revealed on hover/focus (e.g. tooltips) is dismissible, hoverable, and persistent. |
| 2.1.1 | Keyboard | A | All functionality is operable through a keyboard interface with no specific timing. |
| 4.1.2 | Name, Role, Value | A | For all UI components, name, role, and value are programmatically determinable; states and changes are announced. |