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.
The audit credits title, description andsummary on all four custom-chart components and registered recipes rendered through them. Their text contract also applies to React layouts that are outside the JSON prop schema. Unsupported props and unknown renderers still require review; text checks do not establish that a custom layout draws meaningful marks.
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 -p semiotic 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 -p semiotic 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.