We Ran an Accessibility Audit on 10 JavaScript Charting Libraries
A screen reader narrates a web page for blind and low-vision users. It reads the page's underlying structure, not its pixels, which is exactly where charts fall down: most JavaScript charting libraries draw a picture and stop there.
We wanted evidence, not assertions. So we rendered the same default bar chart in 10 popular libraries, ran axe-core against each, and inspected the rendered DOM for the four signals a screen reader depends on: an ARIA role on the graphic, an accessible name, an SVG <title>/<desc>, and a text alternative. Two results stood out. Axe-core reported zero violations for every library, and only 4 of the 10 shipped any accessible structure at all.
Key takeaways
- Automated scanners are blind to charts. All 10 libraries scored zero axe-core violations, including three that render a completely unlabeled
<canvas>. A passing Lighthouse or axe run says nothing about whether a chart is usable. - Most charts ship no accessibility. 6 of 10 libraries render a chart with no role, no accessible name, no title/description, and no data-table fallback by default.
- Two libraries lead by default: ApexCharts and Highcharts are the only ones that attach a role plus an accessible name plus an SVG title/description with zero setup.
- One library ships a data table: Google Charts is the only one that renders a text data-table alternative by default, the single most robust pattern.
- Render technology sets the floor: canvas charts are opaque bitmaps, so a screen reader gets nothing unless the library adds a text alternative. None of the 3 canvas libraries did.
How we tested
We measured out-of-the-box defaults: exactly what a developer gets by following each library's quickstart, with no accessibility add-ons enabled.
- For each library, render a default bar or column chart (5 categories, 5 values) at 600x400 with animations disabled, using the documented "getting started" API and default options.
- Load the page in headless Chromium via Playwright.
- Inject axe-core, run it scoped to the chart container, and record violations by impact.
- Inspect the rendered DOM for objective signals: render technology (SVG or canvas), any ARIA
role, an accessible name (aria-label/aria-labelledby), an SVG<title>/<desc>, focusable elements, and a<table>fallback.
Versions are those resolved on 2026-07-04 and are listed in the scorecard. The Playwright plus axe-core harness is about 150 lines and lives in our repository, so the run is fully reproducible.
What this does not measure. DOM inspection and axe-core are objective and repeatable, but they are the floor, not the ceiling. They do not judge keyboard navigation quality, the exact screen-reader announcement, or prefers-reduced-motion handling. Read a "yes" as necessary, not sufficient.
The scorecard
Only two libraries, ApexCharts and Highcharts, shipped a complete set of default semantics. Six shipped none. Rows are ordered by how much accessible structure each library provides out of the box.
| Library | Version | Render | ARIA role | Accessible name | <title>/<desc> | Data table | axe violations |
|---|---|---|---|---|---|---|---|
| ApexCharts | 5.16.0 | SVG | application | Yes | Yes | No | 0 |
| Highcharts | 11.4.8 | SVG | img | Yes | Yes | No | 0 |
| Google Charts | rolling | SVG | none | Yes | No | Yes | 0 |
| Recharts | 2.15.4 | SVG | none | No | Yes | No | 0 |
| Plotly.js | 2.35.3 | SVG | none | No | No | No | 0 |
| D3 (hand-rolled) | 7.9.0 | SVG | none | No | No | No | 0 |
| Chartist | 0.11.4 | SVG | none | No | No | No | 0 |
| Chart.js | 4.5.1 | Canvas | none | No | No | No | 0 |
| ECharts | 5.6.0 | Canvas | none | No | No | No | 0 |
| amCharts 5 | 5.19.1 | Canvas | none | No | No | No | 0 |
Why most charts fail by default
Automated scanners cannot see charts
This is the finding to internalize first: automated accessibility tools do not evaluate charts. Axe checks that the surrounding page is well-formed, contrast of real text nodes, form labels, landmark structure. It has no rule that asks whether a graphic conveys meaning, so an unlabeled canvas passes as cleanly as a fully described SVG. If your accessibility process ends at automated scanning, charts are a blind spot by construction.
Canvas is a hard floor
Chart.js, ECharts, and amCharts 5 draw to <canvas>. To a screen reader, a canvas is one image with no internal structure to read. Unless the library injects an accessible name, a description, or an offscreen data table, the announcement is empty. None of the three did so by default. This is not a comment on their rendering quality; it is a structural property of canvas that the developer has to compensate for.
SVG is necessary but not sufficient
Seven libraries render SVG, whose elements can carry roles, names, and descriptions. But the capacity is not the delivery: D3, Chartist, and Plotly.js emit SVG shapes with no role, no name, and no title, so a screen reader meets a pile of anonymous <path> and <rect> nodes. SVG is the precondition for an accessible chart, not proof of one.
Only two libraries name the graphic by default
ApexCharts and Highcharts were alone in attaching a role, an accessible name, and an SVG <title>/<desc> with no extra code. That combination is what lets a screen reader announce the chart as a single, named graphic instead of silence or noise.
We will judge our own result honestly. ApexCharts uses role="application", which is aggressive: it pushes many screen readers into an interaction mode meant for widgets like editors, when a static chart is usually better served by role="img" or role="figure". Highcharts uses role="img", the gentler choice. ApexCharts does supply the name and description, which is the hard part, but the role is a fair point of criticism.
A data-table fallback is the most robust pattern
Google Charts was the only library to render a text data table alongside the visual by default. A table is the strongest chart alternative there is: fully navigable, machine-readable, and understood by every assistive technology. More libraries should copy it.
Opt-in accessibility changes the picture
The scores above are defaults, and several libraries offer far more once you turn it on. That distinction is the real lesson, so it is worth stating plainly.
- Highcharts ships a dedicated Accessibility module (a separate script) that adds keyboard navigation, screen-reader descriptions, and a data-table export. Enabled, it is the most complete accessibility story in this list. It is opt-in, so it sat outside this defaults test.
- ECharts has an
ariaoption you enable to generate a describing label. - Chart.js documents that you should supply your own
role,aria-label, and fallback content, because the canvas carries none. - ApexCharts renders accessible SVG semantics by default and documents further guidance in our accessibility docs.
So the takeaway is not "library X is inaccessible." It is that accessibility is usually a feature you have to find and switch on, and defaults are what most projects actually ship.
How to make any chart accessible
You can close most of the gap yourself, in any library, in about ten minutes. These steps are ordered by impact.
- Name the graphic and give it a role. On the chart's root element, add
role="img"(orrole="figure") and anaria-labelthat states the takeaway, not just the title. - Provide a text alternative. A visually hidden data table with the same numbers is the gold standard. Do not use
display:none, which hides it from assistive technology too. - Do not rely on color alone. Separate series with labels, patterns, or direct annotations so the chart survives color blindness and grayscale printing.
- Respect
prefers-reduced-motion. Turn off entrance and transition animations when the user has asked for reduced motion. - Make interactive charts keyboard-reachable. If hovering reveals a tooltip, the keyboard should reach the same values.
- Test with a real screen reader. Automated scans will not catch a silent chart, so run VoiceOver or NVDA once per chart type.
Before and after
The default output of most libraries is a bare container. A screen reader reaches it and finds nothing to announce:
<!-- Inaccessible: the library paints a canvas or a bare SVG into #chart -->
<div id="chart"></div>
Wrapping it gives the graphic a name and a real text alternative. This works with any library, SVG or canvas, and does not wait on the library to add anything:
<figure role="img"
aria-label="Quarterly revenue, rising from 30 to 49 across Q1 to Q5.">
<div id="chart"></div>
<!-- Visually hidden, still read by assistive technology -->
<table class="sr-only">
<caption>Quarterly revenue</caption>
<thead>
<tr><th>Quarter</th><th>Revenue</th></tr>
</thead>
<tbody>
<tr><td>Q1</td><td>30</td></tr>
<tr><td>Q2</td><td>40</td></tr>
<tr><td>Q3</td><td>35</td></tr>
<tr><td>Q4</td><td>50</td></tr>
<tr><td>Q5</td><td>49</td></tr>
</tbody>
</table>
</figure>
/* Hidden from view, still available to screen readers.
Do NOT use display:none or visibility:hidden: those hide it from
assistive technology too. */
.sr-only {
position: absolute;
width: 1px; height: 1px;
margin: -1px; padding: 0; border: 0;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
The role="img" tells the screen reader to treat the visual as one image rather than walk its shapes, the aria-label names it, and the hidden table carries the numbers for anyone who wants them.
How to choose an accessible charting library
Treat accessibility as a selection criterion, not a retrofit. Score any candidate against these five questions before you commit, each answerable in a few minutes.
| Question | Why it matters | How to verify quickly |
|---|---|---|
| SVG or canvas output? | Canvas is opaque to assistive technology; SVG can carry semantics | Inspect the chart: is it <svg> or <canvas>? |
Default role and accessible name? | Without them the chart is announced as nothing, or as noise | Check the root element for role and aria-label |
| Built-in text or data-table alternative? | The most robust way to convey values without sight | Look for an offscreen <table> or a "view as data" control |
| Keyboard access to data points? | Tooltip-only information excludes keyboard and screen-reader users | Tab into the chart: can you reach the series? |
Respects prefers-reduced-motion? | Entrance animation can disorient or nauseate some users | Turn on reduced motion in the OS, then reload |
A "no" is not automatically disqualifying, because the before/after pattern above lets you add the name and the alternative yourself. The goal is to know the gap before you ship, not after a user reports it. When a library offers an opt-in accessibility mode, budget the setup and testing time into your estimate instead of assuming the default is enough.
FAQ
Does a passing axe-core or Lighthouse score mean my charts are accessible? No. In this audit all 10 libraries, including ones that render a completely unlabeled canvas, produced zero axe violations. Automated tools validate page structure, not whether a chart communicates to assistive technology. Charts must be tested manually.
Are SVG charts more accessible than canvas charts? They can be, because SVG elements carry roles, names, titles, and descriptions, while a canvas is one opaque bitmap. But SVG only helps if the library adds those semantics, and several SVG libraries in this test added none.
What is the single most effective thing I can do?
Add role="img" and a descriptive aria-label to the chart container, then provide a visually hidden data table with the underlying numbers. That covers the majority of screen-reader users.
Which libraries were the most accessible by default? ApexCharts and Highcharts were the only two to ship a role, an accessible name, and an SVG title/description with no extra setup. Google Charts uniquely rendered a data-table fallback. With its opt-in accessibility module enabled, Highcharts is the most complete overall.
Why test defaults instead of best-case configuration? Because defaults are what most teams ship. Opt-in accessibility only helps developers who know to look for it, so the links above point to each library's options.
Summary
Chart accessibility is a property you verify, not a library you install. Automated scanners will not flag a silent chart, so their green checkmark is not evidence. Of the 10 libraries tested, only ApexCharts and Highcharts named the graphic by default, only Google Charts provided a data table, and six provided nothing. Whatever you use, the fix is the same and takes minutes: give the chart a role and an aria-label, add a visually hidden data table, respect reduced motion, and confirm with a real screen reader.
To reproduce or extend this audit, run the Playwright plus axe-core harness from our repository against your own chart configuration, then start from our accessibility guide to close any gaps it finds.