Accessible data-visualisation tooling in 2026:
a working stack
Five libraries dominate the modern data-visualisation conversation, but only some of them treat a screen-reader as a first-class consumer. This is a working engineer’s score-sheet, written for teams shipping charts to production in 2026.
1. The five axes that decide whether a chart is accessible
The phrase “accessible chart” hides a stack of quietly different requirements. A bar chart can be rendered as SVG with perfect colour contrast and still be unreachable to a keyboard user. It can be keyboard-navigable and still announce nothing useful to a screen reader. It can announce values cleanly and still drop a sighted low-vision user at the first tooltip. To compare libraries fairly, we evaluate each one against five independent axes that map directly onto how a real assistive-technology user experiences a visualisation.
These five axes are not a personal preference list. They are the practical translation of WCAG 2.2 success criteria (1.4.11 non-text contrast, 2.1.1 keyboard, 4.1.2 name role value), the ARIA Authoring Practices guidance on charts and graphs, and the W3C Research Questions Task Force draft “Data Visualization Accessibility” note that has been circulating since 2023. Every charting library produces SVG; every library renders some kind of legend; every library has some opinion about colour. What separates them is the defaults — the chart you get when you write the smallest sensible amount of code.
1. SVG with semantic ARIA. Does the library output SVG (not canvas), and does that SVG carry meaningful roles, labels, and group structure rather than anonymous <g> nests?
2. Colour-blind-safe palettes by default. Are the categorical and sequential palettes CVD-tested out of the box, or do you have to know to override them?
3. Keyboard-navigable data points. Can a sighted keyboard-only user tab into the chart, arrow between marks, and read each mark’s value?
4. Screen-reader description hierarchy. Is there a title, a one-sentence summary, and per-series / per-point announcements — not just a single alt-text dump?
5. Alternative table view. Is the underlying data available as an HTML table linked from, or rendered next to, the chart for users who prefer tabular consumption?
”A chart that ships with perfect contrast and a colour-blind-safe palette but no keyboard model is a chart you have rendered for half your audience.”
2. The five libraries on the table
Five libraries cover the overwhelming majority of new charting work in 2026: Vega-Lite, Plotly, Observable Plot, Apache ECharts, and D3 with custom code. They occupy different points on the abstraction axis — Vega-Lite is the most declarative, D3 is the most imperative — and each one carries a different posture toward accessibility. We treat D3 separately because “D3 + custom” is a fundamentally different engineering proposition: the accessibility you get is the accessibility you write.
None of these libraries is hostile to accessibility. All of them produce SVG (Plotly and ECharts can also emit canvas; we evaluate the SVG mode). All of them accept arbitrary colour palettes. The question is what you get when you write the smallest sensible amount of code, and how much rewiring it takes to get from that default to a chart that actually clears WCAG 2.2 AA.
The “zero dots” rating for D3 is not a knock on the library — it is the honest description of what you get from a vanilla D3 build. D3 is primitives. Accessibility in a D3 chart is whatever the author writes. A D3 chart authored by an engineer who knows ARIA can be the most accessible chart on the page; a D3 chart authored without that knowledge is almost always the least accessible chart on the page.
3. The scoring matrix: library by accessibility feature
The five axes from section one, scored against the five libraries from section two. “Yes” means the default behaviour clears the axis; “Partial” means the library exposes the right hooks but does not turn them on by default; “Manual” means the engineer has to write the relevant code from scratch.
| Vega-Lite | Plotly.js | Observable Plot | Apache ECharts | D3 + custom | |
|---|---|---|---|---|---|
| SVG output with semantic ARIA | Yes (SVG, titled groups) | Yes (SVG, ARIA labels) | Yes (SVG, mark roles) | Partial (canvas default; SVG renderer opt-in) | Manual |
| Colour-blind-safe palettes by default | Yes (Tableau 10 + viridis) | Partial (Plotly default; CVD palette opt-in) | Yes (Observable categorical10) | Partial (default scheme not CVD-tested) | Manual |
| Keyboard-navigable data points | Partial (focus on legend; marks need config) | Yes (arrow-key navigation in 2.x) | Partial (tip plugin gives focus; marks manual) | Partial (a11y module opt-in) | Manual |
| Screen-reader description hierarchy | Yes (description spec property) | Partial (single title; per-point opt-in) | Yes (ariaLabel + ariaDescription marks) | Partial (a11y module emits per-series alt) | Manual |
| Alternative table view | Partial (data table easy to render) | Partial (export to CSV; no in-DOM table) | Partial (data() helper, no auto table) | Partial (toolbox supports data view) | Manual |
Vega-Lite and Observable Plot lead on declarative defaults. Plotly leads on built-in keyboard navigation. ECharts has the most thorough opt-in accessibility module of any library on the list — but only if you enable it. D3 gives you nothing and everything: every cell is “manual” because the library has no opinion. None of these libraries is a one-line solution; all of them are workable with intent.
4. Good chart, bad chart: the same data, two ways
The matrix shows what each library exposes; this section shows what a working engineer actually writes. Same data, two implementations. The “bad” version ships fast and looks fine on a 27-inch monitor. The “good” version takes 12 more lines of code and clears every axis on the matrix.
// Vega-Lite — defaults only
{
"data": { "url": "complaints.csv" },
"mark": "bar",
"encoding": {
"x": { "field": "category", "type": "nominal" },
"y": { "field": "count", "type": "quantitative" },
"color": { "field": "category" }
}
}Renders. Looks fine. No chart title for the SR. No description. No keyboard model on the marks. Default colour scheme not CVD-tested at the count of categories you actually have. No fallback table.
// Vega-Lite — accessible defaults
{
"title": "Complaints by surface, 2024",
"description":
"Bar chart of 4,605 web-accessibility complaints, ranked by surface. Highest: forms (1,940).",
"data": { "url": "complaints.csv" },
"mark": { "type": "bar", "ariaRoleDescription": "bar" },
"encoding": {
"x": { "field": "category", "type": "nominal",
"axis": { "labelAngle": -30 } },
"y": { "field": "count", "type": "quantitative",
"title": "Complaints" },
"color": {
"field": "category",
"scale": { "scheme": "tableau10" },
"legend": { "title": "Surface" }
},
"tooltip": [
{ "field": "category", "title": "Surface" },
{ "field": "count", "title": "Complaints" }
]
},
"usermeta": { "embedOptions": { "ariaLabel": "Complaints chart" } }
}Title, description, CVD-safe palette, named axis, named tooltip fields, ARIA role description on the mark. Paired with a <table> rendered from the same dataset, this clears every axis on the matrix without leaving the declarative grammar.
The good chart is not a different chart. It is the same chart with the implicit defaults made explicit, the title written down, the palette named, the per-mark role spelled out, and the data also offered as a table. That is the entire art.
None of the five libraries ships a default “render this chart as a table” mode on its own. The working pattern is: bind the same data to two components — the chart and an HTML <table> below it, often hidden visually but exposed to assistive technology with a “Show data table” toggle that flips a hidden attribute. This pattern costs approx. 20 lines of framework code per chart and pays for itself within the first user-research session.
5. Concrete picks, by use case
Library choice in 2026 is mostly about workflow fit. The five libraries on the table are all workable. The question is which one matches the kind of charts you are actually shipping. Five common use cases, five picks, with the second-best alternative named.
Editorial / data-journalism charts (one chart, polished)
Pick: Observable Plot, with Vega-Lite as a close second. Plot’s mark-based API gives you per-mark ARIA labels for free, the categorical palette is CVD-tested, and the SVG output reads cleanly. Vega-Lite is the second-best here because the description property is the cleanest single-attribute screen-reader summary in any library — but Plot wins on default ergonomics for one-off editorial pieces.
Analyst-authored dashboards (many charts, declarative)
Pick: Vega-Lite, with Observable Plot as a close second. Vega-Lite’s specification grammar lets analysts compose 30 charts in one notebook without writing JavaScript, and the schema’s title + description properties give you the screen-reader hierarchy without extra plumbing. Pair every chart with a Vega-rendered data table to clear the alternative-table axis.
Scientific / BI dashboards (interactive exploration)
Pick: Plotly.js, with ECharts as a close second. Plotly is the only library on the list that ships keyboard arrow-key navigation between marks as a default in the 2.x line. If your audience expects to hover, zoom, and drill in, Plotly’s built-in keyboard model is the deciding factor. ECharts catches up if you enable the aria module — but you have to enable it.
High-density operational dashboards (hundreds of points, performance-critical)
Pick: Apache ECharts with SVG renderer + aria module on, with Plotly as a close second. ECharts is the strongest performance story in this group for very dense charts and the aria module produces per-series alt text that screen readers handle competently. Switch off the canvas renderer; canvas is faster but the accessibility tree disappears.
Bespoke editorial charts that no library renders (custom, one-of-a-kind)
Pick: D3 with a hand-written accessibility layer. The hand-written layer is non-negotiable: a <title> + <desc> at the SVG root, per-mark role=“img” with aria-label, a focus model on each focusable mark, and a sibling <table> rendered from the same dataset. D3 is the right tool when the chart genuinely does not exist anywhere else; it is the wrong tool when the chart is a bar chart and someone reached for D3 out of habit.
The chart inside a chart-library is rarely the only thing on the page. Tooltips that hover-only and never appear on focus, legends that are <div> not <ul>, focus rings overridden in the page’s reset stylesheet, colour swatches with insufficient non-text contrast against the page background — these are page-level failures that no charting library will repair for you. The library gives you the marks; the page gives you the rest.
Conclusion: the working stack is the one you write down
None of the five libraries on the table is the wrong answer. All of them clear most axes with a small amount of intent. The single most reliable predictor of an accessible chart in 2026 is not the library on the import line — it is whether the team has written down, in the same place as the design system, what “accessible chart” means at this organisation. Title. Description. Palette. Keyboard model. Alternative table. Five lines in a CONTRIBUTING.md; the difference between a chart that ships and a chart that lands.
Pick the library that fits the workflow, turn its accessibility defaults on, pair every chart with a data table, and audit the page-level chrome around the chart as carefully as the chart itself. The default chart in any of these libraries can be made accessible. The default chart in none of these libraries is accessible without intent.
”The library gives you the marks; the page gives you the rest.”