CHART: two tiers, chosen by what the data does (DR-2026-14)
TIER 1: .tw-chart-bars — CSS comparison bars, no dependency
TIER 2: data-controller="chart" — Chart.js, lazy-loaded, the only sanctioned path
Compare a handful of labelled magnitudes → Tier 1. Change over time, multiple series, axes → Tier 2. One headline number → a Stat Tile, not a chart. A share of a whole → the usage donut. Series colours are computed, not chosen — see the palette section below.
Bars: pure CSS, no JavaScript. Width comes from tw:[--chart-value:64%] —
an arbitrary-property utility, never an inline style. Values use tabular figures so digits line up
down the column. Consolidates the bar treatments hand-rolled across 17 prototype files.
Single series — no legend needed, the title names it.
Multiple series — the legend is mandatory from two.
<div class="tw-chart-bar"><span class="tw-chart-bar-label">Open days</span><span class="tw-chart-bar-track"><span class="tw-chart-bar-fill tw-chart-series-1 tw:[--chart-value:76%]"></span></span><span class="tw-chart-bar-value">76%</span></div>
Small
Large
Stacked labels — long names, narrow card
Plotted data: change over time, multiple series, axes. The only sanctioned path
to Chart.js — no prototype imports it directly. Lazy-loaded on first reveal, so screens without
charts pay nothing; colours resolve from --tw-color-* at runtime, so a token change repaints
every chart. Omit color on a dataset and the ruled order is applied automatically,
and the legend turns itself on from two series.
Two series, smoothed, no explicit colours.
Stacked bars, y axis shown.
<div data-controller="chart" data-chart-type-value="line" data-chart-labels-value='["Sep","Oct"]' data-chart-datasets-value='[{"label":"2026/27","data":[12,19]}]'><canvas data-chart-target="canvas" role="img" aria-label="…"></canvas></div>
The obvious approach — brand tokens at their -500 steps in token order — fails:
orange-500↔green-500 separate by only ΔE 4.0 for deutan viewers, and both fall below
3:1 contrast. Two consequences: the brand navy primary-500 is unusable as a series colour (too dark),
and the series green is green-700, never the base lime.
blue-600orange-700magenta-700green-700primary-400other
Colour alone carries identity to two series. blue-600 + orange-700
measure ΔE 33.3 apart. From three, the legend and direct labels are the identity channel — not an
accessibility garnish. Assign in fixed order, never cycled; beyond five, fold the tail into
.tw-chart-series-other or use small multiples. A sixth hue is never generated.