Focus Indicators for Chart Data Points

Permalink to "Focus Indicators for Chart Data Points"

Once a chart supports keyboard navigation, users need to see where they are. Browser focus rings rarely work: SVG elements may not show them, canvas has none, and a default blue ring on a blue bar is invisible. Chart focus indicators must be designed — a ring or halo that contrasts with both the mark and the background, large enough to spot on a 4-pixel point, layered above neighbouring marks and visible in Windows High Contrast.

This guide belongs to canvas and SVG chart keyboard interaction.

Spec reference

Permalink to "Spec reference"
  • SC 2.4.7 Focus Visible (AA) — a visible indicator for keyboard focus.
  • SC 2.4.13 Focus Appearance (AAA) — indicator area and 3:1 contrast change.
  • SC 1.4.11 Non-text Contrast (AA) — indicator meets 3:1 against adjacent colours.
  • SC 2.4.11 Focus Not Obscured (AA) — the focused mark not hidden by other content such as tooltips.
Focus indicators that fail versus indicators that work Comparison: default outlines or colour changes on marks are often invisible; a two-tone halo drawn above all marks, sized for small points, is visible on any mark and background. Focus indicators that fail versus indicators that work✗ Browser outline (often missing in SVG)Colour change of the mark only1 px ring on a 4 px pointHidden behind neighbouring barsLost in forced colours✓ Custom halo drawn in SVG or canvasRing plus colour change3 px two-tone ring, ≥ 12 px diameterDrawn on top layerUses system Highlight colour

When to use — and when not to

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

Use a custom indicator whenever chart elements are focusable or when the chart tracks a “current” data point with aria-activedescendant.

Do not rely only on changing the mark’s fill colour; it fails colour-blind users and can fail contrast. The misapplication: setting outline: none on SVG elements to remove an ugly default and not replacing it.

Indicator styles by mark type Matrix of indicator styles — two-tone ring, enlarged mark, crosshair, underline bar — against points, bars, line segments and heatmap cells. Indicator styles by mark typePointsBarsLine segmentsHeatmap cellsTwo-tone ringGoodGoodRing the vertexGoodEnlarged markGoodLayout shiftThicker lineOverlapsCrosshairDense plotsBusyGoodGoodUnderline barNoGoodNoOkay
A two-tone ring is the most broadly useful; add a crosshair for dense plots.

Annotated code example

Permalink to "Annotated code example"
/* SVG: two-tone ring visible on light, dark and coloured marks */
.chart .point:focus { outline: none; }                                  /* replace, not remove */
.chart .focus-ring { fill: none; stroke: var(--focus-outer, #111714); stroke-width: 4; pointer-events: none; }
.chart .focus-ring-inner { fill: none; stroke: var(--focus-inner, #ffffff); stroke-width: 2; pointer-events: none; }
@media (forced-colors: active) {
  .chart .focus-ring, .chart .focus-ring-inner { stroke: Highlight; }
}
// Canvas: draw focus after all marks, on every frame that changes focus
function drawFocus(ctx, p) {
  const r = Math.max(p.r + 5, 8);                              // at least 16 px diameter
  ctx.save();
  ctx.lineWidth = 4; ctx.strokeStyle = colors.focusOuter; ctx.beginPath(); ctx.arc(p.x, p.y, r, 0, 2 * Math.PI); ctx.stroke();
  ctx.lineWidth = 2; ctx.strokeStyle = colors.focusInner; ctx.beginPath(); ctx.arc(p.x, p.y, r, 0, 2 * Math.PI); ctx.stroke();
  ctx.restore();
}

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Action Visual Screen reader
Arrow to next point Ring moves, previous ring removed Point label
Focus on a bar Ring around bar Bar label
Tooltip shown Positioned so it does not cover the ring Description
Forced colours on Ring in Highlight colour Unchanged
Chart loses focus Ring hidden —
Contrast of indicator against marks Bar chart of contrast ratios: single blue outline on a blue bar 1.2 to 1; single dark ring on a dark bar 1.5 to 1; two-tone ring outer dark 12 to 1 and inner white 4.8 to 1 on mid-tone bars. Contrast of indicator against marksBlue outline on blue barratioDark ring on dark barratioTwo-tone ring, outerratioTwo-tone ring, innerratio
Two tones guarantee contrast on any mark.

Step-by-step implementation

Permalink to "Step-by-step implementation"
  1. Pick the shape. A ring for points and bars, a vertex ring plus a vertical guide line for line charts, a cell outline for heatmaps.
  2. Use two tones. A dark outer stroke and a light inner stroke ensure 3:1 against at least one, regardless of mark colour.
  3. Size for small marks. Make the ring at least 16 pixels across even if the point is 4 pixels.
  4. Layer on top. Render the indicator in a separate top group (SVG) or after all marks (canvas).
  5. Theme it. Use CSS variables for both tones in light and dark themes and Highlight in forced colours.

Integration context

Permalink to "Integration context"

Keyboard navigation for SVG and canvas charts is covered in adding keyboard navigation to SVG line charts and adding keyboard navigation to canvas bar charts. Tooltips are in accessible tooltips for chart data points. Forced colours are covered in forced colors mode for SVG charts, and auditing in focus appearance 2.4.13 for grid cells.

Gotchas

Permalink to "Gotchas"

:focus-visible in SVG. Support varies; draw your own indicator based on the current data point rather than relying on CSS pseudo-classes alone.

Clipping. Rings near the chart edge can be clipped by the plot area’s clip path; draw them outside it.

Animations. If the ring animates between points, respect prefers-reduced-motion.

Edge cases

Permalink to "Edge cases"
  • Overlapping points. In scatter plots with overlap, the ring should surround the focused point and the label should say how many points share the position.
  • Very large bars. A ring around a bar filling half the chart is hard to notice; add a thicker outline or a marker at the bar’s top.
  • High zoom. Indicators scale with the SVG; check they do not become overwhelming at 400%.

Worked example

Permalink to "Worked example"

A dashboard’s line charts supported arrow-key navigation, but the only feedback was a slightly larger point in the series colour. Low-vision testers could not see which point was focused, especially in dark mode. The team added a two-tone ring on a top layer, a vertical guide line, CSS variables for themes and a Highlight rule for forced colours. Testers located the focused point immediately at every zoom level.

Common mistakes

Permalink to "Common mistakes"
  • outline: none with no replacement.
  • Single-colour rings.
  • Rings drawn under neighbouring marks.
  • Indicators lost in forced colours.

Testing checklist

Permalink to "Testing checklist"

Testing it with a screen reader

Permalink to "Testing it with a screen reader"

Screen reader users rarely need the visual ring, but sighted screen reader users and testers do. With NVDA running, arrow through the chart and confirm the ring moves in step with announcements; a mismatch means focus state and visuals are out of sync. Turn on Windows High Contrast and repeat. Then test with a screen magnifier at 300% to confirm the ring stays in the magnified viewport as focus moves.

Measuring contrast of the indicator

Permalink to "Measuring contrast of the indicator"

Use a colour picker on a screenshot to sample the outer ring, inner ring, mark and background, and compute ratios for each pair. The requirement is that the indicator contrasts 3:1 with what is adjacent to it; with two tones, at least one stroke will meet it against any colour. Automate the check in visual regression tests by capturing the focused state in each theme and comparing sampled colours against a threshold.

Design system notes

Permalink to "Design system notes"

Define chart focus tokens (outer, inner, width, minimum radius) alongside other focus tokens and use them in every chart type.

FAQ

Permalink to "FAQ"
Can I use the browser's default outline?

Only where it reliably appears and contrasts; in charts, a custom indicator is safer.

How thick should the ring be?

At least 2 CSS pixels per tone; 4 plus 2 is a good default.

Should the mark also change colour?

It can, in addition to the ring.

What about touch?

Show the same indicator for the selected point after a tap.

Do canvas charts need this?

Yes, drawn manually.

How do I test contrast?

Sample colours and compute ratios against marks and background.

Permalink to "Related"

← Back to Canvas & SVG Chart Keyboard Interaction