Chart colour
Sequential, diverging and qualitative ramps, why the category and status palettes are both unavailable to a chart series, and how a reference band is coloured.
Overview
opsinjs already has two colour palettes, and the obvious move is to reuse one for charts. Both are wrong, for different reasons, and stating why is the whole point of this page.
The category palette is wrong because it is tuned for the wrong job. Category colours identify a part of someone's health such as sleep or activity, and they are deliberately low in chroma so they can be used as large surfaces and as backgrounds behind text without losing the contrast floor. Low-chroma colours are exactly what you must not use as thin lines that need to be told apart from each other. Two category accents that read as clearly different filling a card are nearly identical as one-pixel strokes.
The status palette is wrong because it means something. Status colour says how much attention this needs. A red line in a chart means "series three". Draw a series in the urgent colour and you have made a clinical assertion about an entire dataset by accident, and simultaneously spent the one colour the system reserves for genuine escalation. See Alarm fatigue.
So charts get a third ramp, generated for discriminability rather than for identity or for urgency, and neither of the other two may appear on a mark.
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.
The third ramp is not built yet
Nothing emits a chart ramp. tokens/color.json carries the neutral ramp, the six
category ramps and the status stems, and no chart ramp, so not one
--opsin-chart-* custom property exists in app/tokens.generated.css.
trend-sparkline takes its stroke from the category axis at the
-line role, which is the interim answer and not the one specified below.
PlannedRoadmapWhat “planned” means
How it works
Three kinds of ramp, and choosing the wrong kind is a bigger error than choosing the wrong hue.
Sequential encodes an ordered quantity with no meaningful midpoint. A count, a duration and an intensity are all quantities of that kind. Lightness does the work; hue barely changes. A sequential ramp read in greyscale must still be ordered, which is the test that catches most bad ones.
Diverging encodes an ordered quantity around a midpoint that means something. It has two hues meeting at a neutral centre and it makes a strong claim: that the centre is a real boundary. In health data that claim is almost always a clinical one, so a diverging ramp may only be used where the midpoint has been set by the product's clinical governance, and it must be labelled. A diverging ramp around an arbitrary midpoint invents a threshold and draws everything on one side of it as though it were a problem. The mean of the visible data is one such midpoint.
Qualitative distinguishes unordered series. This is the one most charts need, it is the hardest to build, and it is where the category palette is most often misused. It is generated by walking hue at a roughly constant lightness and chroma, with each pair checked for perceptual distance and re-checked under protanopia, deuteranopia and tritanopia simulation. The output is a fixed, ordered list in which series one is always the same colour, because a palette assigned in data order means the same series changes colour when the data changes.
Five series is the practical ceiling, and it is a consequence of the above rather than a preference: beyond about five, no set of hues stays reliably distinguishable under all three CVD simulations at the thickness a chart line is drawn at. A chart that needs more series needs small multiples, direct labelling, or fewer series.
Reference bands are neutral, not status-coloured. The shaded region behind a line showing the expected range is drawn as a neutral tint with a visible label and a stated source. Colouring it green would assert that everything inside it is fine, which is a diagnosis; the system does not make diagnoses, and "normal" is a banned word for the same reason. See Reference ranges.
Using it
Take the ramp that matches the data's structure, not the one that looks best. Sequential data in a qualitative palette loses its order. Unordered categories in a sequential ramp acquire an order they do not have, and readers will believe it.
Do
Two metrics on one chart drawn in the first two qualitative chart colours, each labelled directly at its last point, with the reference band in a neutral tint behind them.
Don’t
The same two metrics drawn in their category accents, with the band in the expected-status green. Two axes have been mixed, the category colours are too close to separate at that stroke weight, and the band has quietly told the reader that anything inside it is nothing to worry about.
Never colour a series by its current status. A whole line coloured by the latest reading applies today's judgement to every point in the window, including the ones it does not describe.
Do not use colour as the only difference between series. Use direct labels first, a second channel next, and colour third. The second channel is a dash pattern, a marker shape or a thickness. The greyscale test is the fastest check: print the chart without colour and see whether it still parses.
Pin the assignment. Series one takes chart colour one, deterministically, so a chart does not recolour itself when the reader filters a series out.
Do not tint the plot area. A background wash behind a chart reduces every mark's contrast at once and is the most common cause of a chart failing non-text contrast while every individual colour passes in isolation.
Tokens
The chart ramp is generated from tokens/color.json by
scripts/build-tokens.mts, and it carries sequential, diverging and qualitative
sets, each with its measured pairwise separation and its CVD simulation results
published alongside. It is emitted under its own prefix, distinct from both
--opsin-category-* and --opsin-status-*, so that a mark using the wrong
palette is visible in a stylesheet at a glance.
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
- The qualitative ramp is validated under three CVD simulations, pairwise,
and the results are published rather than asserted.
<CvdSimulator>renders any of it under protanopia, deuteranopia, tritanopia and greyscale; the audit is on Colour independence. - Chart marks are non-text content and are measured as such. WCAG 2.2 SC 1.4.11 Non-text Contrast applies to every line, point and band against its background, and thin marks are the case where a nominally passing colour becomes invisible in practice.
- Colour is never the only difference, in a chart exactly as everywhere else in opsinjs. This is principle 2 from Principles applied to a surface where it is routinely abandoned.
- A neutral reference band is an accessibility decision as well as a clinical one. A coloured band read in greyscale is indistinguishable from a shaded region with no meaning; a labelled neutral one is not.
- Dark mode is derived, not inverted. A chart ramp inverted for dark mode reorders its own lightness and destroys a sequential ramp's ordering. The derivation rules are on Colour → Dark mode.
Related
- The two colour axes covers the two palettes this page is keeping out of charts, and why the separation matters.
- Chart anatomy is the other half of a chart's honesty, and the bigger source of misreading.
- Colour → Colour blindness has the redundancy requirement and the audit this ramp is checked against.
Data visualisation
Charts are three things at once. A foundation with rules, a component family, and an accessibility surface that fails more often than any other.
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.