auditAccessibility() grades a Semiotic chart configuration againstChartability, Frank Elavsky's accessibility framework for data visualization. It organizes 50 heuristics under seven principles —Perceivable, Operable,Understandable, Robust (the WCAG principles) plus Compromising, Assistive, and Flexible — of which 14 are marked critical.
The audit is static: it analyzes the same(component, props) a renderer would, with no DOM and no live assistive technology. That has two honest consequences, and the audit is built around them rather than hiding them:
- Many criticals pass by construction, because every Semiotic HOC ships keyboard navigation, a shape-adaptive focus ring, a skip link, a screen-reader data table (
accessibleTable, on by default), reduced-motion + forced-colors handling, and shareable state. The audit credits these so you see what you already get for free. - Some heuristics can't be settled from config — does the rendered chart actually pass NVDA + Firefox? is the resolved theme contrast ≥ 3:1? Those are reported as
manual with the test to run, not a false pass.
Chartability is explicitly not a pass/fail certification — "you cannot pass Chartability 100%." Treat this audit as triage: it surfaces the author-actionable gaps and routes everything else to the right manual test. Always pair it with real screen-reader testing.
Try it
This runs the real auditAccessibility() in your browser. Switch configurations to see how the findings change.
✗LineChart —8/13 critical heuristics pass · 4 warning(s) · 4 to verify manually
perceivable
✗perceivable.low-contrast [critical] — 1 color(s) fall below 3:1 contrast vs #ffffff: #eeeeee (1.2:1).→ Darken (light background) or lighten (dark background) those colors, or use COLOR_BLIND_SAFE_CATEGORICAL.
✓perceivable.content-only-visual [critical] — A screen-reader data table + live region expose the data non-visually (accessibleTable is on).
✓perceivable.small-text [critical] — Semiotic's default tick and axis-label fonts are 12px, meeting Chartability's 9pt/12px floor.
○perceivable.seizure-risk [critical] — No flashing detected statically. Confirm no element flashes more than 3×/sec.→ Manual check — most static charts pass trivially.
operable
✓operable.single-input-modality [critical] — Built-in keyboard navigation (arrows/Home/End/PageUp-Down/Enter) mirrors mouse hover.
✓operable.tab-stops — The chart takes a single tab stop and navigates data with arrow keys — the recommended pattern for dense charts (no per-datum tab stops to wade through).
⚠operable.interaction-cues [critical] — Chart is interactive but nothing explains how to use it.→ Describe the interaction in a summary or nearby text (keyboard navigation, what hover/click reveals).
✓operable.controls-override-at [critical] — Keyboard handlers fire only while the chart has focus, so they don't hijack page/app screen-reader shortcuts.
✓operable.focus-indicator — A shape-adaptive focus ring (var(--semiotic-focus)) marks the focused element.
understandable
✗understandable.title-summary-caption [critical] — No title, description, or summary — the screen reader falls back to a generic label.→ Add title/description/summary on the chart, or wrap it in a ChartContainer (the opt-in layer for title/caption/description chrome).
✗understandable.explain-purpose [critical] — Nothing explains the chart's purpose or how to read it.→ Add a summary/description, or wrap in a ChartContainer with a title and the describe option (the opt-in full-accessibility layer).
⚠understandable.axis-labels — Missing axis label: xLabel, yLabel. Ticks alone may not name the variable.→ Set xLabel and yLabel to name each axis's variable and units.
robust
○robust.conforms-to-standards — WCAG 2.1 / Section 508 conformance can't be settled from config alone.→ Run an automated checker (axe) on the rendered output and test with real assistive tech.
○robust.semantically-valid — Whether interactive elements expose correct roles/names is a render-time, screen-reader question.→ Verify with a screen reader that buttons read as buttons, the chart as an image/group, etc.
✓robust.fragile-technology-support — Charts render on canvas with an SVG overlay and render to SVG in SSR, so access isn't tied to one rendering path.
compromising
✓compromising.table [critical] — A human-readable data table is provided (accessibleTable).
⚠compromising.table-static — The data table is read-only — not downloadable, sortable, or filterable.→ Wrap the chart in a ChartContainer and enable its data-download action (opt-in, so deployments can withhold it where export isn't allowed).
✓compromising.shareable-state — Chart state serializes via toConfig/toURL/copyConfig.
✓compromising.navigable-structure — Keyboard navigation steps through points and switches series/groups.
assistive
✓assistive.data-density [critical] — 3 data points — a reasonable density for non-visual consumption.
⚠assistive.features-described — No text describes the visually apparent trends, extrema, or outliers.→ Enable ChartContainer's describe option (auto-generates via describeChart()), or write a summary covering the key trend and notable points.
✓assistive.skippable-navigation [critical] — A "Skip to data table" link lets screen-reader users bypass point-by-point navigation.
flexible
✓flexible.user-style-respected [critical] — Styling flows through CSS custom properties and honors forced-colors mode, so user/user-agent style changes cascade in.
✓flexible.reduced-motion — prefers-reduced-motion is auto-detected; transitions fast-forward and looping animation stops.
○flexible.zoom-reflow — Fixed width/height — verify the chart survives browser zoom and reflow without clipping or loss of function.→ Use responsiveWidth/responsiveHeight so the chart reflows to its container.
Statuses
Each finding carries one of five statuses:
- ✓ pass — satisfied, by your config or by Semiotic's built-ins.
- ✗ fail — a problem provable from the config. A critical fail makes the audit
ok: false. - ⚠ warn — a likely problem or a default worth revisiting (it does not block).
- ○ manual — can't be settled statically; run the named Chartability test by hand.
- · not-applicable — the heuristic doesn't apply to this chart (e.g. a data table for a BigNumber).
Programmatic API
JSX
import { auditAccessibility, formatAccessibilityAudit } from "semiotic/utils" const result = auditAccessibility("LineChart", { data: salesData, xAccessor: "month", yAccessor: "sales", title: "Sales by month", }, { inChartContainer: true }) result.ok // false if any critical heuristic FAILS (warn/manual don't block) result.summary // { criticalsPassed, criticalsEvaluated, fails, warnings, manual, passes } result.findings // A11yFinding[] — { id, principle, heuristic, critical, status, message, fix } // Human-readable report (same text the CLI + MCP print): console.log(formatAccessibilityAudit(result))
Pass inChartContainer: true when the chart is (or will be) wrapped in a ChartContainerthat exposes data-download / copy-config affordances — it lets the audit credit the "downloadable table" and "shareable state" heuristics.
CLI
The semiotic-ai CLI runs the audit from JSON on stdin or argv. It exits non-zero when a critical heuristic fails, so it slots into CI as an accessibility gate.
BASH
# Audit a configuration npx semiotic-ai --audit-a11y '{"component":"LineChart","props":{"data":[{"month":1,"sales":10}],"title":"Sales"}}' # Or pipe it in (e.g. from a config generator) echo '{"component":"BarChart","props":{...},"inChartContainer":true}' | npx semiotic-ai --audit-a11y # Exit code: 0 when no critical heuristic fails, 1 otherwise
MCP tool
The same audit is exposed to AI agents as the auditAccessibilityMCP tool, so a model generating a chart can check its own work before handing back code.
JSON
// MCP tool: auditAccessibility { "component": "LineChart", "props": { "data": [...], "xAccessor": "x", "yAccessor": "y", "title": "..." }, "inChartContainer": true } // → the same per-principle report, with the 14 critical heuristics marked
A note on downloadable data
Chartability's "Table/data is static" heuristic asks that the accessible table be downloadable, sortable, or filterable. Semiotic surfaces data download as an opt-in ChartContainer action, never as built-in per-chart functionality. That's deliberate: many deployments must be able to withhold raw-data export (governance, licensing, privacy), and forcing it on every chart would make the toolkit a non-starter for them. So the audit's remediation points you to the ChartContainer action rather than reporting a hard failure.
What it checks
The audit evaluates all 14 critical heuristics plus a curated set of non-critical ones. Highlights by principle:
- Perceivable — contrast (reuses the WCAG math from
diagnoseConfig), default tick-text size, seizure risk, color-only encoding, and CVD-safety (a pass when you opt into the Wong palette). - Operable — keyboard parity, a separate check that complex actions (brush, zoom, legend filtering) have a standard-UI alternative, pointer target size (24px), the single-tab-stop navigation model, interaction cues, AT-shortcut safety, and the focus indicator.
- Understandable — title/description/summary, an explanation of purpose, a Flesch–Kincaid reading-grade estimate, axis labels, dual-axis complexity, statistical-uncertainty communication, and whether changes are easy to follow.
- Robust — manual WCAG / semantic-validity checks, plus credit for rendering that isn't tied to one technology (canvas + SVG overlay, SSR-to-SVG).
- Compromising — the data table, its exportability, shareable state, and navigable structure (flagged for hierarchy charts).
- Assistive — data density (by data points or node count), human-readable number formatting, whether trends/outliers are described in text, and skippable navigation.
- Flexible — user-style respect (CSS variables + forced-colors), animation controls for looping/streaming motion, zoom/reflow support, and adjustable textures.
The "are visually apparent features described?" check is the one most worth your attention. EnablingChartContainer's describe option(auto-generated L1–L3 descriptions via describeChart()) turns that finding from a warning into a pass — pass{ describe: true } to the audit to reflect it.