Accessible charts
The table twin every chart ships with, the generated one-sentence summary, and the markup that stops a chart being an image full of numbers nobody can read.
Overview
A chart is an image whose content is numbers. Alt text works for an image whose content is a picture; it does not work here, because no description of a shape substitutes for the values, and a reader asking "what was my reading on Tuesday" cannot be answered by "a line trending upward".
opsinjs therefore requires a table twin: the same data, in a real table, available to everyone, on the same screen. Not hidden behind a hover, not in a modal, not visually hidden and reachable only by a screen reader. The twin is available. Sighted readers use it too, because the exact value on Tuesday is a question everybody has.
NOT IMPLEMENTED. This component does not exist in any released version of opsinjs. There is no package to install, no module to import and no props interface to generate code against. Everything on this page is a specification of intended behaviour and may change without notice. Do not write code against it.
A chart with a table twin is not built yet
trend-sparkline ships without one: its own source records that the
pointer to a table twin is absent because there is no table twin to point at. No
chart in the registry provides a table twin today, so this requirement is met by
no component. Everything below is the specification one will have to satisfy.
PlannedRoadmapWhat “planned” means
This is a requirement, not a recommendation. A chart component that does not provide a table twin does not meet the definition of done on Component checklist.
How it works
Three things ship together, and none of them is optional.
The chart. The visual mark. It is exposed to assistive technology as a single labelled image whose label is the summary sentence, or it is hidden entirely when the table twin sits immediately beside it. What it must never be is a tree of unlabelled shapes a screen reader walks through one path element at a time.
The summary. One sentence, generated from the data rather than written per chart, stating what the chart shows, over what window, in which direction, by how much, and with what caveat. Generated because a hand-written summary is correct until the data changes and is then silently wrong. The same reasoning produces every other generated artefact on this site. The pattern is roughly: metric, window, direction and magnitude, coverage, caveat. For example: "Systolic blood pressure, last 14 days: 11 readings, ranging from 118 to 134, with no clear direction. Three days have no reading."
The table. Real table markup with a header row, one row per data point, values in the same units and precision the chart uses, and missing readings shown as an empty cell with a stated reason rather than a blank or a zero. It is toggleable where space is short, and the toggle is never a hover. It is a real control that is reachable and labelled.
The order matters. Summary first, then chart, then table. Somebody who reads only the first thing gets the answer; somebody who wants the shape gets it next; somebody who wants Tuesday's number gets it last, which is where they would look anyway.
Interactive charts have a keyboard route. If a point can be hovered to reveal a value, it can be reached with a keyboard and the value is announced. A chart whose data is only available on hover has hidden its data from keyboard users, screen-reader users and everybody on a touchscreen at once.
Using it
Build the table first. It is the data; the chart is a rendering of it. Teams that build the chart first tend to produce a table twin that is a reconstruction of the picture rather than the source, and it goes out of step.
Do
A sparkline with a generated sentence above it and a Show readings control
beneath that expands a table of the fourteen dates and values, with the three
missing days shown as No reading.
Don’t
The same sparkline with alt="Blood pressure chart" and values available
only on hover. Every value in it is unavailable to anybody not using a mouse,
and the alt text tells a screen-reader user nothing they could act on.
Do not describe the shape in the summary. "Trending upward" is an interpretation; "rose from 118 to 134 over 14 days" is the data. Where a direction genuinely matters, it is separated from its valence. See Trends and change. A rise is never described as good or bad by the chart.
State the coverage. How many readings, out of how many expected. A chart drawn from four of fourteen days is a different claim from one drawn from fourteen, and the summary is where that is said.
Do not put the table behind a tab that changes the URL or loses the chart. Both should be reachable without navigating away, so the two can be compared.
Test it by reading the page without looking. Turn the screen off, or turn on a screen reader, and answer two questions: what does the chart show, and what was the value on the third day? If either takes more than a few seconds, the twin is wrong.
Tokens
Accessible charts consume the neutral colour roles for the table, the space scale, and the type scale's value roles for the figures. Those value roles are what make the table's numbers tabular and aligned. There are no tokens specific to this page.
scripts/build-tokens.mts. Run pnpm run generate and reload.Nothing on this site types a measured number by hand, so an ungenerated table shows this rather than an example.
Accessibility impact
- This page is the accessibility impact. The table twin is what makes the data in a chart available to screen-reader users, keyboard users, readers who find shapes hard to interpret, and anybody who wants an exact number.
- WCAG 2.2 SC 1.1.1 Non-text Content is satisfied by the twin, not by alt text. A description of a data graphic is not an equivalent of it; the equivalent is the data.
- SC 1.4.11 Non-text Contrast applies to every mark, and thin lines are the case most likely to pass a spot check and fail in daylight on a phone.
- SC 2.1.1 Keyboard applies to every interactive point. If hovering reveals a value, focusing must reveal the same value.
- The table is a real table. Header cells associated with data cells, a caption naming the metric and the window, and units in the header rather than repeated in every cell. Putting the units in the header is also what makes the table readable when somebody copies it out.
- Missing values are announced as missing. An empty cell that reads as nothing is the data states failure again: absence mistaken for a reading in the usual range.
The tested screen-reader combinations and the announcement contract are on Screen readers; the descriptive-writing guidance is on Alt text and descriptions.
Related
- Chart anatomy covers the axis and window facts the summary sentence has to carry.
- Alt text and descriptions shows how to describe dials, ranges and body diagrams non-visually.
- Data states shows how a missing reading is rendered, in the chart and in the table.
Chart anatomy
Axes, ticks, bands and gaps. The rule against a truncated health y-axis, why the scale is fixed rather than fitted, and what a missing reading must look like.
Sound and haptics
Whether urgency may be carried by a buzz or a chime, the three-pattern haptic vocabulary, and why opsinjs specifies both channels but ships neither.