Hover Cards and WCAG 1.4.13
Permalink to "Hover Cards and WCAG 1.4.13"Hover cards — rich previews that appear when the pointer rests on a link or a name — are common in data products: hover a customer name to see their account summary, hover a ticket ID to see its status, hover a chart point to see its values. They save clicks for mouse users. They also produce some of the most frequent WCAG 2.1+ failures, because the criterion written for them, 1.4.13 Content on Hover or Focus, has three specific requirements that most implementations miss at least one of.
This guide belongs to tooltips, popovers & overlays in data UIs.
Spec reference
Permalink to "Spec reference"SC 1.4.13 Content on Hover or Focus (AA) — where receiving and then removing pointer hover or keyboard focus triggers additional content to become visible and then hidden:
- Dismissible — a mechanism dismisses it without moving pointer hover or keyboard focus (usually Escape), unless it communicates an input error or does not obscure other content.
- Hoverable — if pointer hover can trigger it, the pointer can move over the additional content without it disappearing.
- Persistent — it remains visible until hover or focus is removed, the user dismisses it, or its information is no longer valid.
Also relevant: SC 2.1.1 Keyboard (A) (keyboard users must be able to trigger it if it holds unique information) and SC 1.3.1 (A) (relationship between trigger and card).
When to use — and when not to
Permalink to "When to use — and when not to"Use hover cards as shortcuts to information that is also available elsewhere — the full record page, a detail pane, a table column. They suit mouse users scanning lists.
Do not put unique information or the only path to an action in a hover card. Touch users have no hover; screen reader users rarely trigger it. Do not open cards instantly on hover across a dense grid — a delay of about 300–500 ms prevents a storm of cards as the pointer moves.
The misapplication: a card that disappears as soon as the pointer leaves the trigger, making it impossible to reach the “Open account” link inside it. That fails “hoverable” and makes the link unusable for many mouse users too.
Annotated code example
Permalink to "Annotated code example"<a href="/customers/88" class="hc-trigger" aria-describedby="hc-88">Lund & Co</a>
<div id="hc-88" role="tooltip" class="hover-card" hidden>
<!-- Summary only: the full record is at the link's destination -->
<p>Lund & Co · 12 open invoices · 14,880.00 EUR overdue · Account owner: M. Ortiz</p>
</div>
const OPEN_DELAY = 400, CLOSE_DELAY = 150;
let openT, closeT;
function open(trigger, card) {
clearTimeout(closeT);
openT = setTimeout(() => { position(card, trigger); card.hidden = false; }, OPEN_DELAY);
}
function scheduleClose(card) {
clearTimeout(openT);
closeT = setTimeout(() => { card.hidden = true; }, CLOSE_DELAY); // grace period to cross the gap
}
trigger.addEventListener('pointerenter', () => open(trigger, card));
trigger.addEventListener('pointerleave', () => scheduleClose(card));
card.addEventListener('pointerenter', () => clearTimeout(closeT)); // hoverable (SC 1.4.13)
card.addEventListener('pointerleave', () => scheduleClose(card));
trigger.addEventListener('focus', () => { position(card, trigger); card.hidden = false; }); // keyboard (SC 2.1.1)
trigger.addEventListener('blur', () => { card.hidden = true; });
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !card.hidden) { card.hidden = true; } // dismissible, focus unchanged
});
// Persistent: no auto-hide timer while hovered or focused
Because this card contains only text, role="tooltip" with aria-describedby is appropriate. If it contained links or buttons, it would need to be a click-opened popover or dialog instead, because tooltips must not contain interactive content.
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Event | Expected result | AT deviations |
|---|---|---|
| Tab to “Lund & Co” | Link read, then description: “12 open invoices…” | Description read after a pause |
| Card visible on focus | Yes | — |
| Escape | Card hides; focus stays on link | Escape in a dialog context must not close the dialog too |
| Tab away | Card hides | — |
| Touch | Tap follows the link; card not shown | Information available on the destination page |
Integration context
Permalink to "Integration context"Text-only hover help on truncated cells is covered in accessible tooltips for truncated cell text. Interactive previews belong in the Popover API for data grid overlays. Chart point tooltips are a special case covered in accessible tooltips for chart data points.
Gotchas
Permalink to "Gotchas"Escape order. If a hover card is open inside a modal, Escape should close the card first and the modal only on a second press. Stop propagation when the card handles Escape.
Gaps between trigger and card. A visual gap of even a few pixels lets the pointer “fall off”. Use a close delay, an invisible bridge, or position the card adjacent to the trigger.
Cards on every row. In a 200-row table, hovering down the customer column opens and closes cards constantly. Use an open delay and consider disabling cards in dense modes.
Framework notes
Permalink to "Framework notes"Radix HoverCard supports open and close delays and keeps the card open while hovered; it opens on focus of the trigger, and Escape closes it. MUI Tooltip with disableInteractive={false} (the default since v5) is hoverable. Floating UI offers safePolygon() to keep cards open as the pointer crosses the gap. Verify Escape in each — and remember that none of them makes a card’s unique content accessible to touch users.
<HoverCard.Root openDelay={400} closeDelay={150}>
<HoverCard.Trigger asChild><a href="/customers/88">Lund & Co</a></HoverCard.Trigger>
<HoverCard.Content>12 open invoices · 14,880.00 EUR overdue</HoverCard.Content>
</HoverCard.Root>
Worked example
Permalink to "Worked example"An issue tracker showed assignee hover cards with avatar, team and a “Reassign” button. An audit found three failures: the card closed when the pointer left the name (so Reassign was unreachable), Escape did nothing, and keyboard users never saw the card. The team moved Reassign into the row’s action menu, made the card text-only with a grace period and Escape support, and opened it on focus. The card became a convenience rather than a hidden feature.
Testing checklist
Permalink to "Testing checklist"Design system notes
Permalink to "Design system notes"Offer two components: HoverCard for text-only previews (with 1.4.13 behaviour built in and no interactive children allowed) and PreviewPopover for click-opened rich previews. Lint against interactive elements inside HoverCard.
FAQ
Permalink to "FAQ"Does 1.4.13 apply to the native title attribute?
The title tooltip is browser-controlled; it is generally treated as not author-controlled content. It is still inaccessible in practice, so do not rely on it.
Is Escape the only acceptable dismiss mechanism?
It is the expected one. Any mechanism that does not require moving the pointer or focus counts, but Escape is what users try.
Can hover cards contain links?
Not if they are tooltips. Use a click-opened popover for interactive previews, or duplicate the link elsewhere.
Do hover cards need to work on touch?
Their content does, via another route — usually the link’s destination. The card itself can be a mouse and keyboard enhancement.
What open delay should I use?
300 to 500 ms on hover; immediate on keyboard focus. Too short causes flicker; too long makes the feature feel broken.
Related
Permalink to "Related"- Truncated cell tooltips — text-only hover content in grids
- Popover API for overlays — interactive previews
- Accessible tooltips for chart data points — hover content in charts