Seven approved bases — four dark, three light — now attributed to the register that uses each. Never use an off-palette value as a page or card background. Warm Paper is Commons-only (S-08).
Universal baseline: Systems Blue #007FFF and Forged Steel #475569 are the only two colours approved across every register without restriction. Universal is the default for a mixed or unknown audience, not a fallback of last resort.
Core Deep
#0A0C10
Commons base
Data Midnight
#020617
Bedrock base
Deck Base
#0C1424
Catalyst base
Industrial Surface
#121418
Commons surface
Laboratory Clean v2
#EEF2F6
Bedrock base
White
#FFFFFF
Catalyst base — light
Warm Paper
#FAF7F2
Commons base — S-08
Three registers, each in both modes. Every ratio is the measured contrast against that register's own base. A value is only approved at the size class it was measured at.
Available in every register. Status colours are reserved and never double as a chart series.
Every value in this table is half of a pair. Ship the token, not the hex. Each status colour has a dark value and a light value, and var(--sem-error) / var(--sem-warning) resolve to the right one automatically. Copying a hex out of this table pins it to one mode.
The failure is not subtle, and it runs in the direction people expect least. Warning light is #5B4708, chosen because it clears 7.94 on the light base. Carried onto a dark panel it measures 2.00 — roughly half the contrast of the retired value it replaced. A colour picked to fix contrast makes it worse the moment it crosses modes, because “darker” only helps against light grounds.
So a token carrying two values is not a defect or a duplicate — it is the mechanism. If you find a stylesheet declaring one name twice in different blocks, check whether the blocks are the dark and light scopes before calling it drift.
The exception, and it is not rare: an element that carries its own fixed ground. A callout card with an inline cream background stays cream in both themes. Its text is measured against that card, not against the page — so the literal matched to the card is correct, and a mode-switching token is the wrong answer there. It would flip to the dark-mode value on a ground that never went dark.
The rule is not “always use the token.” It is match the value to the ground the element actually sits on, and use a token exactly when that ground follows the theme. Read the ancestor chain before deciding which case you are in — the background is often several elements up, and a snippet of the element’s own style attribute will not show it.
A related trap when converting: var(--x) where --x is undefined is not a fallback to anything. The declaration is invalid at computed-value time and the element inherits past it. On a page that does not load tokens.css that means silently unstyled text, which is worse than the marginal ratio you were trying to fix. Either supply var(--x, <literal>) or confirm the stylesheet is actually loaded there.
Inversion islands: use the ground-keyed tokens. --sem-* keys off the theme. A contrast requirement keys off the ground. Those are the same thing almost everywhere — which is why the theme-keyed token works, and why the exception is easy to miss.
They come apart at an inversion island: a dark panel pinned inside a light page, or a pale card pinned inside a dark page. There the theme-keyed token resolves to the value chosen for the opposite ground. Measured on a panel pinned to #0f1117 while the theme is light:
var(--sem-warning) → #5B4708 → 2.11 wrong
var(--sem-warning-on-dark) → #FBBF24 → 11.30
It fails in both directions and for all four semantics. So the set now carries --sem-{error,warning,success,info}-on-{dark,light}, defined once outside every theme block so they never switch. Same hexes — these add names, not colours.
Choosing between them: if the surface follows the theme, use the theme-keyed token. If the surface pins its own ground, use the ground-keyed one. The question is never “what mode is the page in” — it is “what is this text actually sitting on.”
A data palette, deliberately separate from the brand palette. The order is load-bearing. Three pairs converge under deuteranopia — blue/violet, olive/orange, pink/teal — and this order keeps them non-adjacent. Assign in order, never cycle. A seventh series folds into “Other”, small multiples, or composite encoding.