a11y(canvas): Tooltip Esc-to-dismiss (WCAG 1.4.13)
WCAG 1.4.13 (Content on Hover or Focus) requires that tooltip content be DISMISSIBLE without moving pointer hover or keyboard focus. Tooltip had no escape hatch — once a keyboard user tabbed onto a control with a tooltip, the tooltip stayed visible until they tabbed away (which moves focus and may not be possible if the tooltip is itself blocking content the user needs to see, e.g. for screen-magnifier users). Add a window-level Escape listener that's active only while a tooltip is shown. Pressing Esc clears the tooltip without moving focus or breaking the hover state, satisfying the dismissible criterion. Used `capture: true` so we beat any modal/dialog Esc handler that might also be listening — the tooltip belongs to the focused control, not the modal it sits inside. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6f203c5646
commit
1d303ee75e
@ -22,6 +22,24 @@ export function Tooltip({ text, children }: Props) {
|
||||
|
||||
useEffect(() => () => clearTimeout(timerRef.current), []);
|
||||
|
||||
// WCAG 1.4.13 (Content on Hover or Focus) — Dismissible: a mechanism
|
||||
// is available to dismiss the additional content WITHOUT moving
|
||||
// pointer hover or keyboard focus. Esc dismisses while the trigger
|
||||
// stays focused/hovered, so a screen-magnifier user can read what
|
||||
// the tooltip was covering without losing their place.
|
||||
useEffect(() => {
|
||||
if (!show) return;
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
clearTimeout(timerRef.current);
|
||||
setShow(false);
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", onKey, true);
|
||||
return () => window.removeEventListener("keydown", onKey, true);
|
||||
}, [show]);
|
||||
|
||||
const enter = useCallback(() => {
|
||||
timerRef.current = setTimeout(() => {
|
||||
if (triggerRef.current) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user