Observable Plot aria Attributes
Permalink to "Observable Plot aria Attributes"Observable Plot is a concise JavaScript library for exploratory and presentation charts, built on D3 by the team behind it. It renders SVG and supports ARIA options directly: ariaLabel and ariaDescription on the plot and on marks, ariaHidden to remove decorative marks, and channel-based ariaLabel so each rendered element (a bar, a dot) can carry its own label. These options make Plot charts readable by screen readers with very little code — if you use them.
This guide belongs to chart library accessibility.
Spec reference
Permalink to "Spec reference"- Plot options —
ariaLabel,ariaDescriptionon the top-level plot setaria-labelandaria-descriptionon the SVG. - Mark options —
ariaLabel(constant or channel function per datum),ariaDescription,ariaHidden. titlechannel — adds SVGtitleelements, shown as native tooltips and often read by screen readers.caption— renders a figure with figcaption.- Relevant criteria: SC 1.1.1, 1.3.1 (A), SC 1.4.1 (A) for series styles.
When to use — and when not to
Permalink to "When to use — and when not to"Use Plot’s aria options on every published chart; they cost one or two lines each. Label marks individually when there are dozens of them; for thousands, hide the marks and provide a table.
The misapplication: a dot plot of 5,000 points each with ariaLabel, creating 5,000 accessibility nodes that slow screen readers without helping anyone.
Annotated code example
Permalink to "Annotated code example"import * as Plot from '@observablehq/plot';
const chart = Plot.plot({
ariaLabel: 'Revenue by region, 2026', // chart name (SC 1.1.1)
ariaDescription: 'Quarterly revenue in million euros. North grew fastest (+18%); South declined in Q3.',
caption: 'North grew fastest (+18%); South declined in Q3.',
x: { label: 'Quarter' },
y: { label: 'Revenue (million euros)', grid: true },
color: { legend: true },
marks: [
Plot.gridY({ ariaHidden: true }), // decorative
Plot.lineY(data, { x: 'quarter', y: 'revenue', z: 'region', stroke: 'region',
strokeDasharray: (d) => d.region === 'South' ? '6,4' : null }), // dash: not colour only
Plot.dot(data, { x: 'quarter', y: 'revenue', fill: 'region', symbol: 'region',
ariaLabel: (d) => `${d.region}, ${d.quarter}: ${d.revenue.toFixed(1)} million euros` }), // per-datum
Plot.ruleY([0], { ariaHidden: true }),
],
});
document.querySelector('#revenue').append(chart);
renderTable('#revenue-table', data); // complete data
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Element | Exposed as | Notes |
|---|---|---|
| Plot SVG | Labelled graphic with description | Read in browse mode |
| Dots with ariaLabel | Labelled elements | Browse mode; not keyboard focusable by default |
| Hidden grid and rules | Not exposed | Less noise |
| Caption | figcaption text | Visible and read |
| Table | Native table | Main route for details |
Integration context
Permalink to "Integration context"Plot’s per-datum labels pair well with keyboard models from keyboard navigation for scatter plots if you add focusability. Summaries follow writing chart summaries and key takeaways. Vega-Lite offers a similar declarative approach in Vega-Lite accessible output.
Gotchas
Permalink to "Gotchas"Interactivity. Plot’s pointer interactions (tips, crosshairs) are pointer-driven; keyboard users need labels in browse mode or a table.
Re-rendering. Plot re-creates the SVG on each render; if you add focus handling, restore focus after re-render.
Legends. Colour legends are images of swatches with text; pair them with direct labels or symbols.
Worked example
Permalink to "Worked example"A research team’s Observable notebooks fed charts into published reports. None had labels; screen reader users heard “graphic”. Adding ariaLabel and ariaDescription to the plot template, ariaHidden on grids, per-datum labels for small charts, symbols per series and a caption with the finding took an afternoon per notebook template. The team also started exporting a CSV link under each chart, which reviewers used more than expected.
A plot wrapper with defaults
Permalink to "A plot wrapper with defaults"export function accessiblePlot({ title, summary, data, marks, ...opts }) {
const plot = Plot.plot({ ariaLabel: title, ariaDescription: summary, caption: summary, ...opts,
marks: marks.map(m => m), });
const figure = document.createElement('figure');
figure.append(Object.assign(document.createElement('h3'), { textContent: title }), plot, tableFor(data));
return figure;
}
Grids and rules created inside the wrapper set ariaHidden: true, and mark labels are added only when the data has fewer than about a hundred rows.
Testing it with a screen reader
Permalink to "Testing it with a screen reader"- Browse to the chart; confirm name and description.
- Explore marks; confirm labels with units for small charts.
- Confirm grids and rules are not announced.
- Check greyscale for series distinction.
- Read the table.
Common mistakes
Permalink to "Common mistakes"- No ariaLabel or description.
- Labelling thousands of marks.
- Exposing decorative marks.
- Colour-only series.
Adoption checklist for teams
Permalink to "Adoption checklist for teams"- Add aria options to a shared plot wrapper.
- Hide decorative marks by default.
- Label marks only below a count threshold.
- Use symbols or dashes per series.
- Generate a table and caption for every published chart.
FAQ
Permalink to "FAQ"Does Observable Plot support ARIA?
Yes, through ariaLabel, ariaDescription and ariaHidden options on plots and marks.
Are marks keyboard focusable?
Not by default. Add focus handling yourself if users need keyboard exploration, or rely on tables.
How many marks should be labelled?
Up to roughly a hundred. Beyond that, hide them and provide a table and summary.
Do title channels help?
They add SVG titles that some readers announce and that show as tooltips; they are not a substitute for ariaLabel.
How do I describe the chart's finding?
Use ariaDescription and a visible caption with the same message.
Can I use Plot in React or Vue?
Yes; render the SVG into a container element and manage re-rendering and focus.
Does Plot support dark mode?
Pass theme-aware colours and check contrast in both themes.
How do I add keyboard exploration to a Plot chart?
Make the SVG focusable, keep an index into the data, highlight the current mark with a separate mark layer, and announce values through a status region on arrow keys.
How do I handle faceted plots?
Give each facet a meaningful label and include facet values in mark labels and the table; consider summarising each facet in the caption.
Do Plot legends support keyboard toggling?
No; build an HTML toggle group if users need to show and hide series.
Related
Permalink to "Related"- Vega-Lite accessible output — a similar declarative approach
- Writing chart summaries — description content
- Keyboard navigation for scatter plots — adding focus