Accessible Tooltips for Truncated Cell Text

Permalink to "Accessible Tooltips for Truncated Cell Text"

Dense grids truncate long values — customer names, descriptions, file paths — with text-overflow: ellipsis. The full text usually appears in a native title tooltip on mouse hover. That leaves out keyboard users (no hover), touch users (no hover), screen magnifier users (tooltip far from the pointer or too small) and, for some screen readers, the tooltip text itself. The good news is that CSS truncation does not remove text from the DOM, so screen readers often read the full value already; the work is in giving sighted keyboard and touch users the same access.

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) — additional content that appears on hover or focus must be dismissible (Escape without moving the pointer or focus), hoverable (the pointer can move onto it without it disappearing) and persistent (stays until dismissed, hover or focus leaves, or it becomes invalid).
  • SC 2.1.1 Keyboard (A) — content available on hover must also be available via keyboard.
  • SC 1.4.10 Reflow (AA) and SC 1.4.12 Text Spacing (AA) — truncation that hides text under zoom or increased spacing must still allow access to the full text.
  • ARIA tooltip role — a popup that describes its owner; referenced from the owner with aria-describedby. The native title attribute is not a substitute: it fails 1.4.13’s dismissible and hoverable requirements in most browsers.
title attribute versus an accessible tooltip Comparison: the native title tooltip appears only on mouse hover after a delay, cannot be hovered or dismissed with Escape, and is invisible on touch; an accessible tooltip appears on hover and focus and meets 1.4.13. title attribute versus an accessible tooltip✗ title attributeMouse hover only, after a delayDisappears when the pointer moves onto itNo Escape to dismissInvisible on touchTiny system font, not zoomable✓ Accessible tooltipHover and keyboard focusHoverable: stays while the pointer is over itEscape dismisses without moving focusShown on focus; tap to focus cellStyled, respects zoom and spacing

When to use — and when not to

Permalink to "When to use — and when not to"

Use a truncation tooltip when a column must stay narrow and values occasionally exceed it — names, titles, short descriptions. Show it only when the text is truncated; a tooltip that repeats fully visible text is noise.

Prefer not truncating at all where you can: allow wrapping (with a maximum number of lines) or let users widen the column, as in keyboard accessible column resizing. Tooltips are a fallback, not a layout strategy.

The misapplication: truncating text and moving the full value into aria-label on the cell. That replaces the cell’s content for screen readers, can break aria-describedby relationships, and does nothing for sighted keyboard users.

Should this cell show a tooltip? Flow: is the text truncated right now? If not, no tooltip. If yes, could the column wrap or be widened instead? If not, show a tooltip on hover and focus that meets 1.4.13. Should this cell show a tooltip?Is the text truncated?scrollWidth greater than clientWidthno: no tooltipCan it wrap or be widened?line clamp, resizable columnyes: prefer thatTooltip neededon hover and focusdescribes the cellMeets 1.4.13Escape, hoverable, persistenttest with keyboard and pointer

Annotated code example

Permalink to "Annotated code example"
<td class="truncate" tabindex="-1" aria-describedby="tip">     <!-- roving focus in a grid -->
  Lund &amp; Co Industrial Supplies — Northern Region Purchasing   <!-- full text stays in DOM -->
</td>
<div id="tip" role="tooltip" class="tooltip" hidden></div>     <!-- one shared tooltip -->
.truncate { max-inline-size: 18ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tooltip { position: fixed; max-inline-size: 40ch; padding: .4rem .6rem; }
const tip = document.getElementById('tip');
const isTruncated = (el) => el.scrollWidth > el.clientWidth;

function show(cell) {
  if (!isTruncated(cell)) { cell.removeAttribute('aria-describedby'); return; }  // only when needed
  tip.textContent = cell.textContent.trim();
  cell.setAttribute('aria-describedby', 'tip');
  position(tip, cell);
  tip.hidden = false;
}
function hide() { tip.hidden = true; }

grid.addEventListener('focusin', (e) => { const c = e.target.closest('.truncate'); if (c) show(c); });  // keyboard (SC 2.1.1)
grid.addEventListener('focusout', hide);
grid.addEventListener('pointerover', (e) => { const c = e.target.closest('.truncate'); if (c) show(c); });
grid.addEventListener('pointerout', (e) => {
  if (!tip.contains(e.relatedTarget)) hide();                     // hoverable (SC 1.4.13)
});
tip.addEventListener('pointerleave', hide);
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !tip.hidden) { e.stopPropagation(); hide(); } }); // dismissible

Because the cell already contains the full text, screen readers read it on focus; the aria-describedby duplicates it for some readers. If that duplication is annoying in your testing, skip aria-describedby for truncation tooltips — the tooltip is then a purely visual aid for sighted users, which is acceptable because the information is already programmatic.

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Event Expected result AT deviations
Arrow onto a truncated cell Full text read; tooltip appears visually With aria-describedby, some readers repeat the text
Escape Tooltip hides; focus stays on the cell Escape must not also cancel cell edit or close a dialog
Pointer moves onto tooltip Tooltip stays —
Arrow to a non-truncated cell No tooltip —
Zoom 200% More cells truncate; tooltips appear for them —
Ways to reveal full cell text Matrix of approaches — title attribute, focus and hover tooltip, line clamp with expand, resizable column — against keyboard access, touch access, 1.4.13 conformance and layout cost. Ways to reveal full cell textKeyboardTouch1.4.13Layout costtitle attributeNoNoFailsNoneHover and focus tooltipYesOn focusPasses if builtwellNoneLine clamp + expandYesYesNot hover contentTaller rowsResizable columnYesYesNot hover contentWider table
Avoiding truncation beats revealing it.

Integration context

Permalink to "Integration context"

Tooltips on cells must coexist with the grid’s focus model — roving tabindex or aria-activedescendant. For help text on column headers, the toggletip pattern for column help is usually a better fit than a hover tooltip. The general rules for hover content are in hover cards and WCAG 1.4.13.

Gotchas

Permalink to "Gotchas"

Escape conflicts. In a grid, Escape may also cancel edit mode or close a filter popover. When a tooltip is open, Escape should close only the tooltip; stop propagation.

Tooltips covering the next row. A tooltip positioned below the cell can hide the row the user is about to read. Position above or to the side, and never over the focused cell.

Virtualized grids. Recycled cells may change text while a tooltip is open. Hide the tooltip on scroll.

Framework notes

Permalink to "Framework notes"

AG Grid has tooltipField and tooltipShowDelay; enable tooltipInteraction: true for hoverable tooltips and verify keyboard focus shows them (recent versions support focus-triggered tooltips). MUI Tooltip shows on focus and hover and closes on Escape; wrap the cell content, not the cell. Angular Material matTooltip appears on focus for focusable hosts; ensure the host is the focused cell.

// MUI: tooltip only when truncated
const [over, setOver] = useState(false);
<Tooltip title={text} disableHoverListener={!over} disableFocusListener={!over}>
  <span ref={el => el && setOver(el.scrollWidth > el.clientWidth)} className="truncate" tabIndex={-1}>{text}</span>
</Tooltip>

Worked example

Permalink to "Worked example"

A procurement grid truncated supplier names at 20 characters and relied on title. A keyboard-only user could not tell “Lund & Co Industrial…” from “Lund & Co Industrial…” in two adjacent rows — two different suppliers with the same prefix. The fix added focus-triggered tooltips for truncated cells, made the Supplier column resizable with the keyboard, and added a density setting that wraps names onto two lines. Most users chose the wrapping density; the tooltip remained for compact mode.

Testing checklist

Permalink to "Testing checklist"

Design system notes

Permalink to "Design system notes"

Make truncation a column option (overflow: 'truncate' | 'wrap' | 'clamp-2') rather than a CSS class teams add ad hoc, and let the grid own the tooltip behaviour. Document that title must not be used for cell content.

FAQ

Permalink to "FAQ"
Do screen readers read text hidden by text-overflow?

Yes. CSS truncation only affects painting; the full text remains in the DOM and the accessibility tree.

Is the title attribute ever acceptable?

As a duplicate for mouse users, it does little harm, but it must not be the only way to get the information, and it does not meet 1.4.13.

Should the tooltip show on focus in browse mode?

Screen reader browse mode does not move DOM focus, so tooltips will not appear — which is fine, because the reader already has the full text.

What delay should the tooltip use?

Around 300–500 ms on hover to avoid flicker; on keyboard focus, show it immediately.

Can a truncation tooltip contain links?

No. A tooltip must not contain interactive content. If users need actions, use a popover or toggletip that is opened deliberately.

Permalink to "Related"

← Back to Tooltips, Popovers & Overlays in Data UIs