Accessibility, export and printing
Generate a text description with the aria option, add decal patterns so colour is not the only channel, export images and print at the right resolution.
The aria option
import * as echarts from 'echarts/core';
import { AriaComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([AriaComponent, CanvasRenderer]);
const option = {
// Turning aria on makes ECharts generate a text description from the
// option itself and attach it to the container as aria-label.
aria: {
enabled: true,
decal: { show: true }, // pattern fills, not just colour
label: {
// Overrides the generated description entirely. Use it when the
// generated text reads badly for your data.
description: 'Revenue by quarter, 2026. Q1 820, Q2 932, Q3 901, Q4 1290.',
general: { withTitle: 'This chart shows {title}. {desc}' }
}
},
title: { text: 'Revenue by quarter' },
xAxis: { type: 'category', data: ['Q1', 'Q2', 'Q3', 'Q4'] },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: [820, 932, 901, 1290], name: 'Revenue' }]
};
// The generated description is a summary, not a data table. For anything a
// user must read precisely, provide a real table in the page and link to it.
const chart = echarts.init(el);
chart.setOption(option);
el.setAttribute('role', 'img');
el.setAttribute('aria-describedby', 'revenue-table-caption');| Technique | What it achieves | Limit |
|---|---|---|
aria: { enabled: true } | A generated text description on the container | Summarises; it is not a data table |
aria.label.description | Full control over the wording | You must keep it in sync with the data |
aria.decal: { show: true } | Pattern fills per series | Only helps if the patterns are distinguishable |
| A visible data table | Exact values for everyone | Duplicates the chart in markup |
A hidden table with visually-hidden | Screen readers get the numbers | Not visible to sighted keyboard users |
role="img" plus a description | Tells assistive tech it is one graphic | Cards and canvases are otherwise unlabelled |
| Keyboard-accessible neighbours | Filters and tooltips reachable by Tab | ECharts internals are not keyboard navigable |
⚠️
A canvas chart is invisible to assistive technology. The
aria option adds a text summary to the container, which is a real improvement, but it does not make the data explorable. If the values matter to a decision, publish them in a table — either visible or behind a "show data" disclosure.Decal patterns and focus
const option = {
aria: { enabled: true, decal: { show: true } },
// Decals can also be defined per series for full control
series: [
{
type: 'bar',
name: 'Revenue',
data: [820, 932, 901, 1290],
itemStyle: {
decal: {
symbol: 'rect',
symbolSize: 1,
color: 'rgba(255, 255, 255, 0.5)',
dashArrayX: [1, 0],
dashArrayY: [2, 5],
rotation: Math.PI / 6
}
}
},
{
type: 'bar',
name: 'Cost',
data: [410, 480, 455, 610],
itemStyle: {
decal: {
symbol: 'circle',
symbolSize: 0.6,
color: 'rgba(255, 255, 255, 0.6)',
dashArrayX: [1, 0],
dashArrayY: [1, 0]
}
}
}
]
};/* Give the chart container a visible focus style and a sensible role. */
.chart-container:focus-visible {
outline: 2px solid var(--brand, #6d28d9);
outline-offset: 2px;
}
/* Never hide a chart from assistive tech with aria-hidden when it carries data. */
.chart-container { position: relative; }
/* Respect reduced motion: ECharts listens to its own option, not to CSS. */
@media (prefers-reduced-motion: reduce) {
/* Nothing to do in CSS — set animation: false in the option instead. */
}// Honour the reduced-motion preference in the option itself.
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
function animationOption(matches) {
return matches
? { animation: false }
: { animation: true, animationDuration: 400, animationEasing: 'cubicOut' };
}
chart.setOption({
...baseOption,
...animationOption(prefersReducedMotion.matches)
});
prefersReducedMotion.addEventListener('change', (event) => {
chart.setOption(animationOption(event.matches));
});Export and printing
// A PNG of the current chart, at twice the pixel density for print.
const png = chart.getDataURL({
type: 'png',
pixelRatio: 2,
backgroundColor: '#ffffff', // transparent by default; set it for print
excludeComponents: ['toolbox'] // keep the toolbar out of the image
});
// An SVG string, when vector output matters.
const svg = chart.renderToSVGString();
// Building a downloadable link without a server round trip
function download(url, filename) {
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
}
download(png, 'revenue-q4.png');
// Several charts on one dashboard, combined into a single image:
// getConnectedDataURL stitches the charts that were connected with
// echarts.connect(groupId) into one output.
import { connect } from 'echarts/core';
connect('dashboard'); // every chart that called chart.group = 'dashboard'
const combined = chart.getConnectedDataURL({
type: 'png', pixelRatio: 2, backgroundColor: '#ffffff'
});| Goal | API | Note |
|---|---|---|
| Crisp PNG for print | getDataURL({ pixelRatio: 2 }) | Also set backgroundColor so it is not transparent |
| Vector output | renderToSVGString() | Requires the SVG renderer |
| Save from the UI | The toolbox saveAsImage feature | Configure the filename and pixel ratio |
| Whole dashboard as one image | getConnectedDataURL | Charts must be in the same group |
| A PDF report | Export each chart, then compose server-side | Do the layout where you control the page size |
| A print stylesheet | CSS @media print | A canvas prints at its rendered size; increase the ratio instead |
| Copy the underlying data | chart.getOption().series | The option, not the transformed data |
// A print path that produces a usable page rather than a screenshot.
window.addEventListener('beforeprint', () => {
// Re-render at a higher device pixel ratio so the printed output is sharp.
chart.setOption({}, false);
chart.resize({ width: 900, height: 500 });
});
window.addEventListener('afterprint', () => {
chart.resize(); // back to the container's size
});
// If the chart is off-screen when the user prints, it may never have rendered.
// Force a render before the print dialog opens:
function ensureRendered(chart) {
const el = chart.getDom();
if (el.clientWidth === 0) return;
chart.resize();
}- A canvas exports at the size it is rendered. Raising
pixelRatiois the only way to get more detail from a small chart. - Exclude the toolbox from exports. A downloaded chart with a save button in the corner looks like a mistake.
- Set
backgroundColorexplicitly. The default is transparent, which turns black in some viewers and makes a JPEG unusable. - For a report containing several charts, export each one and compose the document in the tool that controls the page — do not try to lay out a PDF inside a canvas.
FAQ
Does the aria option make my chart accessible?
It makes it announced rather than invisible, which is a real improvement, but the description is a summary. If the exact values matter, publish them in a table and link the chart to it with
aria-describedby.Why is my exported image transparent or black?
The default background is transparent, and some image tools render transparency as black. Pass
backgroundColor: '#ffffff' to getDataURL, and raise pixelRatio if the output looks soft.Related
Styling, themes and dark mode Tooltips, legends, labels and visual maps
Last refreshed 2026-09-18.