Audit any chart's accessibility in one command
Functional checks — keyboard, live region, text alternative, contrast — against any chart on any page. No integration, no signup.
npx -p fcharts-js -p playwright fcharts-audit \
--target https://your.app/dashboard --selector '#chart'
That loads the page in headless Chromium, scopes to the element you pointed at, and runs the functional check battery a markup scanner can't: it presses the keys, reads the accessibility tree, measures the rendered pixels. Report-only — it never gates or blocks anything on a page you don't own — and it works on charts from any library: Chart.js, ECharts, uPlot, Highcharts, a hand-rolled canvas.
What it actually checks
- Keyboard: is there a focusable chart surface at all? Do arrow keys move a data cursor? Is there keyboard zoom? Does Escape dismiss? Can focus leave (no trap)?
- Screen-reader wiring: a polite live region that actually changes on navigation; name/role/description on the surface; canvas hidden from the tree so it doesn't read as a mystery object.
- Text alternative: a real data table with scoped headers — and tick labels that exist as findable text, not pixels.
- Rendered-pixel facts: computed contrast for text and marks on the actual background, target sizes, fluid sizing and legend reflow, reduced-motion / forced-colors / focus-visible handling.
- Plus a scoped axe-core pass — necessary, never sufficient (see below).
What a real run looks like
Here's the (lightly trimmed) output of a run against the public demo of a widely used dashboard product's time-series panel — a fast, canvas-based chart, exactly the kind teams ship every day:
checks: 5 pass · 17 fail · 13 n/a → report written to ./audit-out
✗ 17 functional check(s) failed on this target:
✗ keyboard-announce: no [role="application"] surface to receive keyboard focus
✗ live-region-present: no aria-live=polite aria-atomic region
✗ text-alternative: no data table with caption + scoped headers
✗ canvas-hidden: canvas missing aria-hidden=true
✗ keyboard-zoom · escape-dismiss · no-keyboard-trap · focus-visible · …
axe-core: 0 serious/critical violations
Read that last line again. Zero axe violations — and seventeen functional failures. The scanner gave this chart a clean bill of health because a canvas has almost no markup to scan. This is the single most important thing to understand about chart accessibility: scanner-clean and screen-reader-usable are different claims, and only one of them is the one your users (and auditors) care about.
From diagnostic to CI gate
Target mode is a diagnostic. For charts you own, the same engine runs as a regression
gate: --fixture mode mounts your configured chart, re-proves every
automatable claim against a committed per-criterion baseline, regenerates the
VPAT/ACR (EN 301 549, WCAG, and
Section 508 editions), and fails the build if any criterion regresses. There's a
GitHub
Action that wraps both modes, and fcharts-audit --compare old.json new.json
diffs two reports so a reviewer sees exactly what changed in conformance between
versions.