Skip to content

fix(tooltip): dismissible + hoverable per WCAG 1.4.13#3363

Merged
cixzhang merged 1 commit into
mainfrom
navi/fix/tooltip-wcag-1413
Jul 2, 2026
Merged

fix(tooltip): dismissible + hoverable per WCAG 1.4.13#3363
cixzhang merged 1 commit into
mainfrom
navi/fix/tooltip-wcag-1413

Conversation

@cixzhang

@cixzhang cixzhang commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Part of the accessibility & keyboard-management program (#3343). Fixes finding overlays-5 (Tooltip WCAG 1.4.13).

Problem

Tooltip failed both prongs of WCAG 1.4.13 (Content on Hover or Focus):

  • Not dismissible. There was no Escape handler anywhere in useTooltip, so a keyboard user who focused a trigger could not dismiss the tooltip without moving focus away.
  • Not hoverable. mouseleave on the trigger hid the tooltip at the default hideDelay of 0, with no listeners on the tooltip surface — so the pointer could not travel across the gap onto the tooltip content to read or interact with it.

Fix

  • Escape to dismiss. A document-level keydown listener hides the tooltip on Escape while it is uncontrolled. Guarded against IME composition-cancel (isComposing / keyCode === 229). layer.hide() self-guards, so this is a no-op when the tooltip is already closed.
  • Hoverable surface. A short hover bridge (100ms when no explicit hideDelay is set) plus onMouseEnter/onMouseLeave handlers on the tooltip container keep the tooltip open while the pointer is over it. These handlers are attached to the layer container itself (the element the user hovers), not the inner content div, because mouseenter/mouseleave do not bubble.
  • useLayer primitive. ContextRenderProps gains optional onMouseEnter/onMouseLeave so any hover-driven layer (Tooltip, HoverCard) can implement hoverable behavior through the shared primitive rather than reaching around it.

Controlled tooltips (isOpen prop set) are unchanged — their visibility stays owned by the consumer.

Tests

Adds three Tooltip.test.tsx cases: Escape dismisses while visible; Escape during IME composition does not dismiss; the tooltip stays open when the pointer moves onto the surface. Full suite green (10 Tooltip + 52 Layer/Popover/HoverCard), typecheck clean.

@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview, Comment Jul 2, 2026 8:26am

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 2, 2026
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.6KB 0B

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

github-actions Bot added a commit that referenced this pull request Jul 2, 2026
Adds Escape-to-dismiss (IME-guarded) and a hover bridge so the pointer can
travel from the trigger onto the tooltip surface without it vanishing.
useLayer context render props gain onMouseEnter/onMouseLeave on the container.
// `layer.isOpen` (React state, which can lag a frame behind the DOM) —
// `layer.hide()` self-guards and no-ops when the layer is already closed.
// Guarded against IME composition-cancel.
useEffect(() => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing this in a few different PRs plus we want to ensure that only top layer gets dismissed before cascading to other layers. We should coordinate these in a followup.

@cixzhang cixzhang marked this pull request as ready for review July 2, 2026 15:31
@cixzhang cixzhang enabled auto-merge (squash) July 2, 2026 15:31
@cixzhang cixzhang merged commit bc2f7c9 into main Jul 2, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants