Chart Library Accessibility

Permalink to "Chart Library Accessibility"

Chart libraries differ more in accessibility than grid libraries do. Some ship comprehensive modules — keyboard navigation of points, screen reader sections, legend toggle buttons, sonification. Others offer a single option or nothing at all. And every library’s defaults leave the most important parts to you: what the chart is about, what its numbers mean, and how a user who cannot see it gets the data. A dashboard built from a well-configured library with summaries and tables can be fully usable without sight; the same dashboard with defaults is a row of unlabelled images.

This topic compares five widely used libraries and shows how to configure each: Highcharts, Recharts, Apache ECharts, Vega-Lite and Observable Plot. The users most affected are screen reader users (names, descriptions, data access), keyboard users (exploring values), colour-blind users (series distinction) and low-vision users (text size, contrast, zoom). The failure this topic prevents is assuming a library is “accessible” because it has an option somewhere, without configuring it or adding what it cannot provide.

This topic belongs to accessible data component libraries.

WCAG criteria in scope

Permalink to "WCAG criteria in scope"
Criterion Level Library features that help What you add
1.1.1 Non-text Content A Descriptions, ARIA output Summaries and tables
1.3.1 Info and Relationships A Labelled axes and legends Tables with headers
1.4.1 Use of Color A Decals, dash styles, symbols Direct labels
1.4.11 Non-text Contrast AA Theme options Verified palettes
2.1.1 Keyboard A Highcharts and Recharts navigation Wrappers for others
4.1.2 Name, Role, Value A Legend toggles (some libraries) HTML toggles elsewhere

Prerequisites

Permalink to "Prerequisites"
Chart libraries and their built-in accessibility Matrix of libraries — Highcharts, Recharts, ECharts, Vega-Lite, Observable Plot — against chart description, keyboard navigation, colour-independence options, data table and legend toggles. Chart libraries and their built-in accessibilityDescriptionKeyboardColour optionsData tableLegend togglesHighchartsModulePoints andseriesDash, patternsView tableButtonsRechartsAddaccessibilityLayerDashes via propsAddAddEChartsaria optionAddDecalsdataViewAddVega-LitedescriptionAddstrokeDash,shapeAddBound inputsObservable Plotaria optionsAddSymbols, dashesAddAdd
Highcharts provides the most; every library needs summaries and tables from you.

ARIA & HTML spec reference

Permalink to "ARIA & HTML spec reference"
Output Where it comes from Notes
Chart name Library option or wrapping figure Always provide
Long description Caption, aria-describedby, library description Human-written summary preferred
Point labels Highcharts points, Plot/Vega descriptions Limit on dense data
Keyboard focus Highcharts, Recharts layer, your wrapper One tab stop per chart
Legend buttons Highcharts, your HTML aria-pressed
Data table Library view or your HTML Complete and labelled
Graphics roles Vega output Support varies

Step-by-step implementation

Permalink to "Step-by-step implementation"

Step 1 — Inventory

Permalink to "Step 1 — Inventory"

List each library in use, its version, and its accessibility features.

Step 2 — Enable built-in features

Permalink to "Step 2 — Enable built-in features"
// Highcharts: module loaded; ECharts: aria + decal; Recharts: accessibilityLayer; Plot: aria options

See Highcharts accessibility module configuration, Recharts accessibility and ECharts aria and decal patterns.

Step 3 — Summary and table for every chart (SC 1.1.1, 1.3.1)

Permalink to "Step 3 — Summary and table for every chart (SC 1.1.1, 1.3.1)"
<figure aria-labelledby="c-h" aria-describedby="c-sum"><h3 id="c-h">…</h3><p id="c-sum">…</p>…<table>…</table></figure>

Step 4 — Keyboard access (SC 2.1.1)

Permalink to "Step 4 — Keyboard access (SC 2.1.1)"

Use built-in navigation where it exists; elsewhere add a focusable wrapper that moves through points and announces values.

Step 5 — Colour independence (SC 1.4.1)

Permalink to "Step 5 — Colour independence (SC 1.4.1)"

Dashes, symbols, decals and direct labels — configured per library. See Vega-Lite accessible output and Observable Plot aria attributes.

Step 6 — Shared defaults

Permalink to "Step 6 — Shared defaults"

Wrap each library in a defaults helper or component so every chart inherits the configuration.

Choosing a chart library for accessibility Flow: need rich built-in keyboard and screen reader support? Highcharts. React with simple charts? Recharts with additions. Large dashboards with patterns? ECharts. Declarative specs and notebooks? Vega-Lite or Observable Plot. In every case: summary, table and colour independence. Choosing a chart library for accessibilityRich built-in supportkeyboard, sections, legendsHighchartsReact, standard chartsquick to buildRecharts + layer + additionsLarge dashboardsdecals and descriptionsECharts + keyboard wrapperDeclarative or notebooksspecs as dataVega-Lite or PlotAlwayssummary, table, patternsyour wrapper

Keyboard interaction contract

Permalink to "Keyboard interaction contract"

Aim for the same contract across libraries so users learn it once:

Key Action Libraries with built-in support
Tab Into the chart (one stop) Highcharts, Recharts (layer); others via wrapper
Left / Right Previous / next point Highcharts, Recharts; others via wrapper
Up / Down Previous / next series Highcharts; others via wrapper
Enter on legend item Toggle series Highcharts; HTML toggles elsewhere
Escape Leave chart navigation Highcharts; wrapper

Screen reader compatibility matrix

Permalink to "Screen reader compatibility matrix"
Library NVDA JAWS VoiceOver Notes
Highcharts Good Good Good Configure descriptions and units
Recharts Tooltip announcements with layer Same Same Add figure and table
ECharts Description only Same Same Add keyboard and table
Vega-Lite Description; marks in browse mode Partial Partial Table recommended
Observable Plot Labels in browse mode Partial Partial Table recommended

Edge cases & failure modes

Permalink to "Edge cases & failure modes"

1. Module not loaded

Permalink to "1. Module not loaded"

Diagnosis: Highcharts accessibility module missing from the bundle. Fix: include it in the shared chart setup.

2. Generated descriptions too long

Permalink to "2. Generated descriptions too long"

Diagnosis: ECharts lists dozens of values. Fix: human summaries.

3. Canvas renderers

Permalink to "3. Canvas renderers"

Diagnosis: no mark semantics; ignores forced colours. Fix: SVG where possible; tables always.

4. Colour-only series

Permalink to "4. Colour-only series"

Diagnosis: defaults everywhere. Fix: decals, dashes, symbols, labels.

5. Dense marks exposed

Permalink to "5. Dense marks exposed"

Diagnosis: thousands of labelled nodes. Fix: hide dense layers; provide tables.

Highcharts accessibility module configuration

Permalink to "Highcharts accessibility module configuration"

Descriptions, units, keyboard order, screen reader sections and verbosity thresholds turn good defaults into useful output. See Highcharts accessibility module configuration.

accessibility: { point: { valueSuffix: ' million euros' } }

Recharts accessibility

Permalink to "Recharts accessibility"

accessibilityLayer for keyboard tooltips; figures, summaries, dashes, labels and tables around it. See Recharts accessibility.

<LineChart data={data} accessibilityLayer>…</LineChart>

ECharts aria and decal patterns

Permalink to "ECharts aria and decal patterns"

aria descriptions and decal patterns built in; keyboard and tables added by you. See ECharts aria and decal patterns.

aria: { enabled: true, decal: { show: true } }

Vega-Lite accessible output

Permalink to "Vega-Lite accessible output"

Descriptions, axis titles, mark descriptions, SVG rendering and tables generated from the spec’s data. See Vega-Lite accessible output.

"description": "Quarterly revenue by region, 2026."

Observable Plot aria attributes

Permalink to "Observable Plot aria attributes"

ariaLabel, ariaDescription and ariaHidden on plots and marks, with tables for dense data. See Observable Plot aria attributes.

Plot.gridY({ ariaHidden: true })

Cross-cutting concerns

Permalink to "Cross-cutting concerns"

One chart wrapper. Whatever the library, wrap charts in a component that requires a title and summary, renders a table toggle, provides keyboard navigation where missing, and applies colour-independent series styles. The wrapper is where consistency comes from.

Mixed libraries. Dashboards often mix libraries. Align keyboard behaviour and summary placement across them, even if internals differ.

Dark mode and forced colours. Each library themes differently; verify palettes and decals in both themes, and remember that canvas rendering ignores forced colours.

Upgrades. Accessibility features change between versions; re-test after upgrades, especially for libraries adding features quickly.

A chart inside a shared wrapper Sequence among wrapper, library chart, table and announcer: the wrapper renders heading and summary; the library draws the chart with accessibility options; the user arrows through points via built-in or wrapper navigation; values are announced; the table toggle shows the full data. A chart inside a shared wrapperWrapperLibrary chartTableAnnouncerheading and summaryrender with a11y optionsarrow keys: "Q3, North: 4.6 million euros""Show data table"
The wrapper guarantees what libraries leave out.

Worked example: a dashboard with three chart libraries

Permalink to "Worked example: a dashboard with three chart libraries"

A product had grown a dashboard with Highcharts (finance widgets), ECharts (heatmaps) and Recharts (product analytics). Accessibility varied wildly: Highcharts widgets announced points but had no descriptions; ECharts heatmaps were unlabelled canvases; Recharts charts were hover-only. The team built a ChartFigure wrapper used by all three: required title and summary, a table toggle, a status region, and a keyboard adapter for ECharts. Each library got shared defaults (Highcharts units and order, ECharts aria and decals with SVG, Recharts accessibilityLayer with dashes). The dashboard now behaves consistently, and the table toggles became the most-used feature among analysts.

Who is affected, and how

Permalink to "Who is affected, and how"
User group Library default failure Fix
Screen reader users Unlabelled graphics; no data Summaries and tables
Keyboard users Hover-only values Built-in or wrapper navigation
Colour-blind users Colour-only series Decals, dashes, symbols, labels
Low-vision users Small canvas text; contrast SVG rendering; verified palettes
Everyone Unclear message Visible summaries

Evaluating a chart library

Permalink to "Evaluating a chart library"

Before adopting a chart library, build one line chart and one bar chart with realistic data and check: can you name and describe the chart? Can a keyboard user reach values? Can you distinguish series without colour? Can you produce a table from the same data easily? Does SVG rendering exist? How does the library behave in dark mode and forced colours? Spend fifteen minutes with a screen reader. A library that needs only a thin wrapper to pass all of these is a good choice; one that needs extensive workarounds will cost you on every chart.

An audit script for existing dashboards

Permalink to "An audit script for existing dashboards"

List every chart and its library. For each: check the accessible name in the accessibility tree; check for a visible summary; check for a table; try the keyboard; take a greyscale screenshot. Record results in a simple table. Most dashboards show the same two gaps — no summaries and no tables — which a shared wrapper fixes across all charts at once.

Choosing between libraries for new work

Permalink to "Choosing between libraries for new work"

For new dashboards, pick the library whose built-in accessibility covers the most of what you need, then standardise on it. Teams prioritising accessibility with minimal custom code often choose Highcharts; teams wanting open-source and declarative specs often choose Vega-Lite or Observable Plot with a wrapper; React teams with simple needs often choose Recharts with the accessibility layer and a wrapper. Whatever the choice, the wrapper is not optional.

Design system integration

Permalink to "Design system integration"

Provide one ChartFigure component and per-library defaults modules. Require summaries, tables and series styles; supply keyboard adapters for libraries without navigation; and document which library to use for which chart types. Keep a compatibility table of library versions and verified features.

Common mistakes with chart libraries

Permalink to "Common mistakes with chart libraries"
  • Options never enabled (modules not loaded, aria off).
  • Generated text instead of summaries.
  • No table.
  • Canvas everywhere.
  • Different keyboard behaviour per library.

Measuring chart accessibility

Permalink to "Measuring chart accessibility"

For every chart in a product, record: summary present, table available, keyboard access, colour-independent series, and last screen reader check. A product-wide percentage for each column shows where to invest, and per-library breakdowns show which wrappers need work.

A minimal accessible chart, regardless of library

Permalink to "A minimal accessible chart, regardless of library"

Every chart in a product should meet the same minimum: a heading that names it; a one- or two-sentence visible summary of its main finding; a way to see the data as a table; series distinguishable without colour; values reachable from the keyboard either in the chart or in the table; and a focus indicator wherever focus can land. If a library makes any of these hard, the wrapper supplies it. Publishing this minimum as a checklist in the design system documentation turns chart accessibility from a library debate into a clear definition of done.

Testing checklist

Permalink to "Testing checklist"

Automated

Permalink to "Automated"

Keyboard

Permalink to "Keyboard"

Screen reader and visual

Permalink to "Screen reader and visual"

FAQ

Permalink to "FAQ"
Which chart library is most accessible?

Highcharts has the most comprehensive built-in support. Others can be made accessible with wrappers, summaries and tables.

Is a data table enough?

A table is a complete equivalent for the data; a summary conveys the message; keyboard exploration helps in place. Provide all three where possible.

Should I use canvas or SVG?

SVG for most dashboards; canvas for very large datasets, with tables as the main accessible route.

Do generated descriptions satisfy WCAG?

They name and partly describe the chart, but a human summary of the finding is far more useful.

How do I handle many charts on one page?

Headings per chart, consistent summaries and keyboard behaviour, and tables behind toggles.

How do I test chart libraries quickly?

Turn on a screen reader, tab to each chart, arrow through points, toggle a series, open the table — the same five steps for every library.

Should we standardise on one library?

It helps consistency, but a shared wrapper can align behaviour across several.

What about sonification?

Highcharts supports it; it is a useful addition for trends, never a replacement for tables.

Who should own chart accessibility?

The team that owns the chart wrapper and library defaults, usually the design system or a data visualisation platform team, with product teams writing summaries for their charts.

How do we write good summaries at scale?

Generate the factual parts (largest, smallest, change) from data and let authors add the interpretation for important charts.

Are library accessibility modules enough for procurement?

They support conformance claims only when configured and tested in your product. Report actual behaviour in your ACR, not library marketing.

What should we do with charts we cannot fix soon?

Add a summary and a table first; they make the information available even before keyboard and colour work is done.

Permalink to "Related"

← Back to Accessible Data Component Libraries