Tooltips, Popovers & Overlays in Data UIs

Permalink to "Tooltips, Popovers & Overlays in Data UIs"

Data interfaces lean heavily on overlays because space is scarce. Truncated cells reveal full values in tooltips; column headers hide definitions behind info icons; customer names open hover cards; filter panels, column menus and row actions pop over the grid; date pickers float under inputs. Each overlay saves screen space for sighted mouse users. Each is also a place where keyboard users get stuck, screen reader users miss information, touch users have no way in, and magnifier users see content vanish as they pan towards it.

WCAG 2.1 introduced SC 1.4.13 Content on Hover or Focus precisely because hover content had become so common and so frequently unusable. But 1.4.13 is only part of the story. Overlays also need correct roles (a menu is not a dialog is not a tooltip), focus that moves in and comes back, DOM placement that keeps reading order sane, and — most importantly — information and actions that do not exist only inside something that disappears.

The users most affected are keyboard users (hover-only content), touch users (no hover at all), screen reader users (unnamed popovers, tooltips that are never exposed, menus with no keyboard model), magnifier users (content that disappears when the pointer moves towards it) and users with motor impairments (hover cards that close before the pointer reaches them). The failure mode this topic prevents is information and functionality that exist only for steady-handed mouse users.

This topic belongs to core ARIA & keyboard navigation for data UIs.

WCAG criteria in scope

Permalink to "WCAG criteria in scope"
Criterion Level Relevance to overlays
1.3.1 Info and Relationships A Trigger–overlay relationships; roles for overlay content
1.3.2 Meaningful Sequence A Overlay content read after its trigger
1.4.13 Content on Hover or Focus AA Dismissible, hoverable, persistent hover content
2.1.1 Keyboard A Overlays openable and operable without a pointer
2.4.3 Focus Order A Focus moves in and returns logically
2.4.11 Focus Not Obscured (Minimum) AA Overlays must not hide the focused element
2.5.8 Target Size (Minimum) AA Info icons and overlay triggers
4.1.2 Name, Role, Value A Expanded state and roles exposed

Prerequisites

Permalink to "Prerequisites"
The overlay spectrum Matrix of overlay types — tooltip, toggletip, hover card, popover, modal dialog — against how it opens, whether it may contain controls, whether it traps focus and its role. The overlay spectrumOpens onControls insideTraps focusRoleTooltipHover and focusNeverNotooltipToggletipClick or EnterText onlyNostatus bubbleHover cardHover and focusText onlyNotooltipPopoverClick or EnterYesNodialog, menu orlistboxModal dialogClick or EnterYesYesdialog with aria-modal
Pick the type first; behaviour follows from it.

ARIA & HTML spec reference

Permalink to "ARIA & HTML spec reference"
Element / attribute Valid values When to apply Common misuse
role="tooltip" + aria-describedby — Text-only hover or focus help Interactive content inside
title Text Rarely; never as the only source Truncated text and help
popover auto, manual, hint Transient overlays in the top layer Treating it as modal
popovertarget ID Buttons that open popovers Divs as triggers
aria-expanded true / false Disclosure-like triggers Not updated on light dismiss
aria-haspopup menu, dialog, listbox Triggers of those overlays true on everything
role="menu" / menuitem — Action menus Navigation link lists
dialog / showModal() — Blocking tasks Non-blocking panels

Step-by-step implementation

Permalink to "Step-by-step implementation"

Step 1 — Classify the overlay

Permalink to "Step 1 — Classify the overlay"

Decide which of the five types it is before choosing a library component. Most bugs come from using a tooltip component for something that is really a popover.

// Decision helper used in code review
const kind = hasControls ? (blocksPage ? 'modal' : 'popover') : (opensOnClick ? 'toggletip' : 'tooltip');

Step 2 — Make hover content conform (SC 1.4.13, 2.1.1)

Permalink to "Step 2 — Make hover content conform (SC 1.4.13, 2.1.1)"
trigger.addEventListener('focus', show); card.addEventListener('pointerenter', cancelClose);
document.addEventListener('keydown', (e) => e.key === 'Escape' && hide());

See hover cards and WCAG 1.4.13.

Step 3 — Toggletips for help (SC 4.1.2, 4.1.3)

Permalink to "Step 3 — Toggletips for help (SC 4.1.2, 4.1.3)"
<button type="button" aria-label="About Risk score" aria-expanded="false">i</button><span role="status"></span>

See the toggletip pattern for column help.

Step 4 — Truncation with focus tooltips (SC 1.4.13)

Permalink to "Step 4 — Truncation with focus tooltips (SC 1.4.13)"
if (cell.scrollWidth > cell.clientWidth) showTooltip(cell);

See accessible tooltips for truncated cell text.

Step 5 — Popovers with semantics (SC 4.1.2)

Permalink to "Step 5 — Popovers with semantics (SC 4.1.2)"
<div id="filter-status" popover="auto" role="dialog" aria-labelledby="fs-h">…</div>

See the Popover API for data grid overlays.

Step 6 — In-place DOM with anchor positioning (SC 2.4.3, 1.3.2)

Permalink to "Step 6 — In-place DOM with anchor positioning (SC 2.4.3, 1.3.2)"
[popover] { position-anchor: --trigger; position-area: block-end; }

See anchor positioning and focus order for overlays.

Classifying an overlay Decision flow: does it contain controls? If yes, does it block the page? Modal dialog or popover. If no controls, does the user open it deliberately? Toggletip. Otherwise, tooltip or hover card meeting 1.4.13. Classifying an overlayContains controls?links, buttons, inputsyes: popover or dialogBlocks the page?unsaved edits, confirmationyes: modal dialogOpened deliberately?info button, helpyes: toggletipOpens on hover or focustext onlytooltip or hover card, meets 1.4.13

Keyboard interaction contract

Permalink to "Keyboard interaction contract"
Overlay Key Action Expected AT announcement Failure indicator
Tooltip Focus trigger Show Trigger name, then description Nothing on focus
Tooltip / hover card Escape Hide, focus stays — Escape closes the dialog behind
Toggletip Enter Show help Help text via status region Silent
Popover Enter Open, focus inside Role and name of popover Focus stays on trigger, nothing said
Popover Escape Close, focus to trigger Trigger name Focus to body
Popover Tab past end Next control after trigger Next control Page footer
Modal Tab Cycles inside — Escapes to page

Screen reader compatibility matrix

Permalink to "Screen reader compatibility matrix"
AT + browser tooltip via describedby Popover role announced Toggletip status Notes
NVDA + Chrome Read after name Yes, when focus moves in Read Reliable
NVDA + Firefox Read after name Yes Read —
JAWS + Chrome Read; verbosity dependent Yes Read Tooltip may need a pause
VoiceOver + Safari Read after pause Yes Read Verify focus return after light dismiss
TalkBack + Chrome Inconsistent Yes Read Hover content unreachable; plan alternatives
Narrator + Edge Read Yes Read —

Edge cases & failure modes

Permalink to "Edge cases & failure modes"

1. Interactive content in a tooltip

Permalink to "1. Interactive content in a tooltip"

Diagnosis: a “Reassign” button inside a hover card. Fix: move actions to a popover or the row menu.

2. Escape closes everything

Permalink to "2. Escape closes everything"

Diagnosis: one Escape closes the tooltip and the dialog behind it. Fix: stop propagation when an inner overlay handles Escape.

3. Overlay covers the focused cell

Permalink to "3. Overlay covers the focused cell"

Diagnosis: a tooltip positioned over the next row hides focus as the user moves down. Fix: position away from the reading direction; check SC 2.4.11.

4. Portalled menus break Tab order

Permalink to "4. Portalled menus break Tab order"

Diagnosis: Tab from a menu lands in the page footer. Fix: in-place DOM with the top layer, or managed focus.

5. Help only on hover

Permalink to "5. Help only on hover"

Diagnosis: column definitions only in header tooltips. Fix: toggletips and a definitions dialog.

Accessible tooltips for truncated cell text

Permalink to "Accessible tooltips for truncated cell text"

Full text stays in the DOM; a tooltip appears on hover and focus only when text is truncated; Escape dismisses; wrapping or resizing is preferred. See accessible tooltips for truncated cell text.

.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

Toggletip pattern for column help

Permalink to "Toggletip pattern for column help"

An info button named with the column opens help on click, announced through a status region, separate from the sort control. See the toggletip pattern for column help.

bubble.textContent = ''; requestAnimationFrame(() => { bubble.textContent = help; });

The Popover API for data grid overlays

Permalink to "The Popover API for data grid overlays"

Top layer, light dismiss and Escape come free; roles, names, initial focus and keyboard models do not. See the Popover API for data grid overlays.

pop.addEventListener('toggle', (e) => e.newState === 'open' && pop.querySelector('input,button').focus());

Hover cards and WCAG 1.4.13

Permalink to "Hover cards and WCAG 1.4.13"

Dismissible with Escape, hoverable with a grace period, persistent without timeouts, opened on focus — and never holding unique information. See hover cards and WCAG 1.4.13.

card.addEventListener('pointerenter', () => clearTimeout(closeT));

Anchor positioning and focus order

Permalink to "Anchor positioning and focus order"

Keep overlays next to their triggers in the DOM and position them with anchor positioning in the top layer, so Tab and reading order stay local. See anchor positioning and focus order for overlays.

.trigger { anchor-name: --t; } [popover] { position-anchor: --t; }

Cross-cutting concerns

Permalink to "Cross-cutting concerns"

Overlays inside grids. In a role="grid", triggers live in cells and arrow keys belong to the grid. When an overlay closes, return focus to the trigger and make its cell the grid’s current tab stop. When an overlay is open, the grid’s key handler must stand down.

Overlays and sticky elements. Sticky headers and toolbars can cover overlays, and overlays can cover the focused cell. Test with keyboard navigation near the top and bottom edges of a scrolled grid; SC 2.4.11 requires focus not to be entirely hidden.

Zoom and reflow. At 400% zoom, overlays should fit the viewport, scroll internally if needed, and not require two-dimensional scrolling. Anchor positioning’s fallbacks (position-try-fallbacks) help.

One Escape at a time. Nested overlays — a date picker in a filter popover in a dialog — need a clear Escape order: innermost first.

Escape through nested overlays Sequence: with a date picker open inside a filter popover inside a dialog, the first Escape closes the picker, the second the popover, the third the dialog; focus returns one level each time. Escape through nested overlaysDialogFilter popoverDate pickerPageEscape 1: picker closes;focus to date fieldEscape 2: popover closes;focus to filter buttonEscape 3: dialog closes; focus to its trigger
Each Escape closes exactly one layer.

Worked example: auditing a grid’s overlays

Permalink to "Worked example: auditing a grid’s overlays"

An audit of an orders grid found seven overlay types: truncation tooltips (via title), header definitions (hover tooltips on sort buttons), customer hover cards (with a Message button), column filter panels (portalled divs), row action menus (portalled), a date picker in the filter panel, and a bulk-edit dialog. The fixes, in order of impact: column definitions moved to toggletips; the Message button moved from the hover card into the row menu, and the card became text-only with Escape and focus support; filter panels and row menus were rebuilt as in-place popovers with role="dialog" and role="menu"; the date picker became a descendant of the filter popover so opening it no longer closed the panel; truncation used focus tooltips and a wrap density. Only the bulk-edit dialog was already correct, because it used showModal().

Who is affected, and how

Permalink to "Who is affected, and how"
User group Typical failure Fix
Keyboard users Hover-only content; Tab escapes to page end Focus triggers; in-place DOM
Touch users No hover at all Click-opened toggletips and popovers
Screen reader users Unnamed popovers; tooltips never exposed Roles, names, describedby
Magnifier users Content vanishes as the pointer approaches Hoverable, persistent content
Users with tremor Hover cards close before reached Grace periods; click alternatives

Measuring overlay quality

Permalink to "Measuring overlay quality"

Inventory first: list every overlay in the product and classify it with the decision flow above. In most audits a third of overlays turn out to be the wrong type — usually tooltips that should be popovers. Then track two numbers per release: overlays with unique content or actions (target zero), and overlays that fail the four-step 1.4.13 check (hover, move onto it, Escape, keyboard focus). Both are quick to measure manually and catch the failures that matter most.

Design system integration

Permalink to "Design system integration"

Provide one component per overlay type — Tooltip, Toggletip, HoverCard, Popover (with FilterPanel, Menu variants) and Dialog — rather than one configurable Overlay. Build them on the Popover API and anchor positioning with documented fallbacks; enforce “no interactive children” in Tooltip and HoverCard with a lint rule; and centralise Escape handling so nested overlays close one at a time. Store column help in column definitions so toggletips, descriptions and definition dialogs share text.

Testing checklist

Permalink to "Testing checklist"

Automated

Permalink to "Automated"

Keyboard

Permalink to "Keyboard"

Screen reader and pointer

Permalink to "Screen reader and pointer"

FAQ

Permalink to "FAQ"
Are tooltips bad for accessibility?

Tooltips with short, non-essential, text-only help that open on focus and hover and meet 1.4.13 are fine. The problems come from putting essential information or actions in them.

What is the difference between a popover and a dialog?

A dialog is a role; a popover is a presentation mechanism. A popover can contain a non-modal dialog, a menu or a listbox. A modal dialog uses showModal() instead.

Should overlays open on hover in data grids?

Sparingly, with delays, and never as the only way. Click-opened overlays are more predictable for everyone.

How do I handle overlays on touch devices?

Make every overlay openable by tap (toggletips, popovers) and ensure hover-only content is duplicated elsewhere.

Do I still need a positioning library?

For browsers without anchor positioning, yes, as a fallback. The Popover API removes the need for portals and z-index management.

How do I test 1.4.13 quickly?

Hover the trigger, move the pointer onto the overlay (it must stay), press Escape (it must close without moving the pointer), then focus the trigger with the keyboard (it must open).

Which overlay bug should I fix first?

Anything that hides an action or unique information inside hover content. Those block tasks; the rest slow them down.

Can the same trigger open both a tooltip and a popover?

Avoid it. A button that shows a tooltip on hover and a menu on click confuses users and screen readers alike. Give the button a clear visible label instead of a tooltip, and let it open the menu.

Permalink to "Related"

← Back to Core ARIA & Keyboard Navigation for Data UIs