Accessible Tooltips for Chart Data Points

Permalink to "Accessible Tooltips for Chart Data Points"

A chart tooltip is the small box that shows a data point’s exact values — “March: 1,240 orders, up 8%”. In most chart libraries it appears on mouseover and vanishes on mouseout. For keyboard users who arrow through the data, it never appears; for low-vision users who hover and then move the pointer to read a tooltip that is partly off-screen, it vanishes as they approach; for screen reader users it is either silent or read twice.

This page makes the tooltip a presentation of the chart’s focus state, following SC 1.4.13 Content on Hover or Focus. It belongs to canvas & SVG chart keyboard interaction.

Spec reference

Permalink to "Spec reference"

SC 1.4.13 Content on Hover or Focus (Level AA) applies to additional content that appears on pointer hover or keyboard focus and hides again. It must be:

  • Dismissible — closable without moving hover or focus, usually with Escape, unless it obscures nothing.
  • Hoverable — the pointer can move over the tooltip without it disappearing.
  • Persistent — it stays until hover or focus is removed, the user dismisses it, or its information is no longer valid.

The ARIA tooltip role describes a popup that displays a description for an element, associated through aria-describedby. For chart points, the tooltip usually carries the point’s primary information rather than a supplementary description, so the more reliable approach is to put the same text in the point’s accessible name (SVG) or a polite live region (canvas), and keep the visual tooltip aria-hidden to avoid double reading.

Related: SC 2.1.1 Keyboard (values available without a pointer), SC 1.4.3 and 1.4.11 (tooltip text and border contrast), SC 2.4.11 Focus Not Obscured (the tooltip must not hide the focused point).

One focus state, three presentations Layers showing a chart's single focus state driving the visual focus ring, the visual tooltip and the text for assistive technology. One focus state, three presentationsFocus statewhich point is current — set by arrow keys or by pointer hoverFocus ringdrawn on the point; 3:1 against the plot areaVisual tooltippositioned clear of the point; hoverable; Escape dismisses; aria-hiddenText for ATpoint's accessible name (SVG) or a polite live message (canvas)
Hover and keyboard both set the same focus state; everything users perceive is derived from it.

When to use a tooltip — and when a persistent readout

Permalink to "When to use a tooltip — and when a persistent readout"

Use a tooltip for occasional detail on a chart whose shape carries the main message. It keeps the chart clean.

Use a persistent readout panel — a fixed area beside the chart that always shows the focused point’s values — for charts users explore intensively, such as financial time series. It never covers the data, never needs dismissing, and is easier to read at high zoom. Many analytics tools offer both.

The misapplication to name is making the tooltip itself focusable and moving focus into it. Focus leaves the chart, arrow-key navigation stops, and the user has to find their way back.

Annotated code example

Permalink to "Annotated code example"
// A single focus state drives ring, tooltip and announcement
const state = { index: null, dismissed: false };

function setFocusPoint(i, source) {          // source: 'keyboard' | 'pointer'
  state.index = i; state.dismissed = false;
  render();
  if (source === 'keyboard') announce(pointText(i));   // pointer users see the tooltip
}

function render() {
  drawChart();
  if (state.index === null) return hideTooltip();
  const p = points[state.index];
  drawFocusRing(p);                                    // SC 1.4.11
  if (!state.dismissed) showTooltip(p);
}

function showTooltip(p) {
  tip.textContent = pointText(p.i);
  tip.hidden = false;
  // SC 2.4.11: place clear of the point; flip if near an edge
  const { x, y } = toScreen(p);
  const above = y - tip.offsetHeight - 16 > 0;
  tip.style.left = `${Math.min(Math.max(x - tip.offsetWidth / 2, 8), chartW - tip.offsetWidth - 8)}px`;
  tip.style.top = `${above ? y - tip.offsetHeight - 16 : y + 16}px`;
}

// SC 1.4.13 hoverable: moving onto the tooltip does not hide it
let hideTimer;
chart.addEventListener('pointerleave', () => { hideTimer = setTimeout(clearIfNotHovered, 300); });
tip.addEventListener('pointerenter', () => clearTimeout(hideTimer));
tip.addEventListener('pointerleave', () => { hideTimer = setTimeout(clearIfNotHovered, 300); });

// SC 1.4.13 dismissible: Escape hides it without moving focus
chart.addEventListener('keydown', (e) => {
  if (e.key === 'Escape' && !tip.hidden) { state.dismissed = true; tip.hidden = true; e.stopPropagation(); }
});
<div class="chart" tabindex="0" role="group" aria-label="Orders per month, 2026">
  <canvas aria-hidden="true"></canvas>
  <!-- visual only: the same text reaches AT through the live region -->
  <div class="chart-tip" aria-hidden="true" hidden></div>
</div>
<p class="visually-hidden" role="status" id="chart-live"></p>

Announcing only for keyboard-driven changes is deliberate. Screen reader users navigate with the keyboard; announcing every pointer hover would speak whenever a sighted colleague moved the mouse across a shared screen, and would flood speech for screen magnifier users who also run a reader.

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Event Visual Announcement
Arrow to a point Ring + tooltip appear “March: 1,240 orders, up 8%.”
Escape Tooltip hides; ring stays None
Next arrow Tooltip reappears for the new point New point announced
Pointer hover Ring + tooltip None (visual only)
Pointer moves onto tooltip Tooltip stays None
Focus leaves chart Ring and tooltip hide None
Library-default tooltip versus accessible tooltip Comparison of a typical library tooltip driven by mouseover against a tooltip driven by the chart's focus state that meets SC 1.4.13. Library-default tooltip versus accessible tooltip✗ Library defaultAppears on mouseover onlyDisappears when the pointer moves toward itCovers the hovered pointText never reaches screen readers✓ Focus-driven tooltipAppears for keyboard and pointer focusHoverable and persistentPositioned clear of the pointSame text announced for keyboard moves
The fix is architectural: the tooltip follows focus state, not the mouse.

Integration context

Permalink to "Integration context"

The focus state here is the same one maintained by the chart’s keyboard model — see adding keyboard navigation to canvas bar charts and keyboard navigation for scatter plots. The focus ring’s colour follows non-text contrast for chart series.

With Chart.js, Highcharts and similar libraries, disable the built-in tooltip’s default trigger and call the library’s API to show it at the focused index from your keyboard handler; accessible Chart.js configuration shows the Chart.js calls.

Checking SC 1.4.13 on a chart tooltip Four manual checks for a chart tooltip against SC 1.4.13 and keyboard access: keyboard trigger, hoverable, persistent, dismissible. Checking SC 1.4.13 on a chart tooltipKeyboard triggerarrow to a point: does the tooltip appear?SC 2.1.1Hoverablemove the pointer onto the tooltip: does it stay?SC 1.4.13Persistentwait ten seconds: is it still there?SC 1.4.13Dismissiblepress Escape: does it hide without moving focus?SC 1.4.13
Four checks, each one a clause of the criterion.

Gotchas

Permalink to "Gotchas"

Tooltips at 400% zoom. A tooltip sized in pixels can be larger than the zoomed chart. Size it in rem, cap its width, and let text wrap.

Animated entrances. Fade and slide animations on every arrow press are distracting and slow. Disable them under prefers-reduced-motion.

Stale tooltips. If the data refreshes, the tooltip’s text may no longer be valid; SC 1.4.13 lets it disappear then. Re-render or hide it.

Design system notes

Permalink to "Design system notes"

Charts in a design system should share one ChartTooltip component driven by a focus state object, with tokens for background, border and text that meet contrast in both themes, a max width in rem, and built-in Escape and hover handling. Products then supply only pointText(i), which is also the announcement.

Testing checklist

Permalink to "Testing checklist"

FAQ

Permalink to "FAQ"
Do chart tooltips need to work with the keyboard?

Yes. If exact values are shown only in a hover tooltip, keyboard users cannot get them, which fails SC 2.1.1. Show the tooltip when a point receives keyboard focus, from the same state that drives hover.

What does SC 1.4.13 require of chart tooltips?

That they can be dismissed without moving focus or pointer, usually with Escape; that the pointer can move over the tooltip without it disappearing; and that it stays visible until focus or hover moves away or the user dismisses it.

Should the tooltip be announced by screen readers?

Its information should be, once. Put the text in the focused point’s accessible name or a polite live message, and mark the visual tooltip aria-hidden so it is not read a second time.

Should focus move into the tooltip?

No. Keep focus on the chart so arrow-key navigation continues. The tooltip is a presentation of the focused point, not a separate stop.

Permalink to "Related"

← Back to Canvas & SVG Chart Keyboard Interaction