ADR-0105 A status colour is two roles, and the palette is authored in P3
#Context
The maintainer looked at /foundations/color beside the reference's and said the critical and caution colours read as skin tones. They do. status-critical resolved to #ffcdbe in interior-dark and #eca992 in field-night, and status-caution to #f2d5ad and #b17661.
The first three explanations were all wrong, and measuring them is what found the real one.
Not the curve's parameters. Every combination of peak chroma and falloff exponent that stays inside the generator's own clamp sanity bound was searched. Eight qualify. The best delivers 4% more chroma than the shipped config, which is invisible.
Not the curve's shape. Replacing the parabola with a gamut-aware curve that asks for 90% of whatever sRGB holds at each lightness produced *less* saturation at the steps that matter: #e74500 became #df5023. At its mid steps this ramp was already sitting on the sRGB boundary.
Not mainly the gamut. Display P3 holds 13% more chroma than sRGB at L 0.65 and 29% more at L 0.85, measured at hue 37. Real, and biggest exactly where the ramps wash out, but 0.108 chroma at L 0.85 is still a pale colour. A pale step is pale in any gamut.
The actual cause is that one token was doing two jobs. A status role is drawn two ways: a glyph or a word drawn *in* the colour, and a chip or a bar drawn *as* the colour. The first is chosen for contrast against the page, the second for saturation. On a light substrate those answers nearly coincide. On a dark one they are opposites, because contrast wants lightness and chroma runs out at high lightness. With one token the contrast requirement wins, because text has a floor and a fill does not. So the token goes pale, and every filled surface in the system goes pale with it.
interior-dark's critical ramp holds chroma 0.196 at step 3 and 0.061 at step 6. The single token sat at step 6. That gap is several times anything the gamut or the curve was ever going to give.
#Decision
- **Three new roles:
status-critical-bold,status-caution-bold,
status-positive-bold.** Saturation led, bound to the most chromatic step each ramp offers in each theme. They are for filled grounds: chips, bars, an indicator's body. The existing status-* roles are unchanged and stay contrast led, for anything drawn in the colour rather than as it.
- The bold roles are surfaces at level 0, not marks. A fill is a plane,
which is what scrim and the borders already are. This is not a way around ADR-0039's ladder: emission is measured off rendered pixels and never read off a declaration, so a component that paints a loud fill is priced for it whatever its token claims. What the slot settles is which tier may reference the role, and a ground belongs with the grounds.
- The palette is authored against Display P3 and echoed in sRGB. The
oklch() value is fitted to P3, the hex to sRGB. toCss already emitted both under one custom property, so a browser that cannot parse oklch() keeps the hex and every browser that can takes the second declaration and renders it at the display's own gamut. No media query, no second sheet, one number per token for the budget to price.
- Contrast floors are checked against the sRGB echo, not the P3 value. A
pair that clears its floor in the narrower gamut clears it in the wider one, and the reverse is not true. Checking the fallback is the only order that cannot ship a theme legible on the author's display and not on the reader's.
- The clamp sanity bound moves to the authoring gamut. The claim it
protects is that a ramp came from its curve rather than from a gamut wall, and that claim now belongs to P3. The sRGB echo may clamp: that is what a fallback is.
- Peak chroma rises 10%, which is the headroom moving to P3 opened
underneath the same bound. Worst clamp 0.0658 against the bound of 0.08.
cautionmoves from hue 75 to 85. Hue 75 is olive, and at the chroma
this system can afford it read as khaki in all four themes rather than as amber.
field-night's two statuses move to different rungs. They were both on
step 4. Under ADR-0006 that theme has one hue and lightness is its only semantic axis, so two statuses on one rung were separated by nothing but a chroma difference that the same record says carries no information.
#Consequences
Every semantic and component token is regenerated and every budget baseline moves. The measured deltas are around 1e-4 per scene and no scene crosses its ceiling, but the values are re-recorded, so this lands as an intentional re-baseline.
Recorded afterwards, 2026-09-18. The paragraph above re-recorded the baselines and missed the ceilings, which are derived from the ramps this record moved rather than measured from a scene. field-night's neutral ladder rung moved 0.093997 to 0.093724 and its emission ceiling 0.128712 to 0.128439, and packages/budget/src/config.js kept the pre-P3 pair, so derive-ceiling.js had been failing its own agreement check since this record landed. The three other themes were unaffected. Corrected under ADR-0036's rule that a value in config.js disagreeing with receipts/ceiling-derivation.json is a defect; no scene changed outcome, and the two scenes over a ceiling are still the two intentional violation fixtures.
All thirteen components that carry a status now draw from a ground: status-indicator, tag, meter, alert, toast, trend, staleness, sla-countdown, progress-indicator, inline-loading, button, input-field and number-input. Each sets a --ground property beside --mark, and the two resolve alike at every rung but Marked, so only that one rung moves.
Which parts take a ground was decided by how they are sized rather than by eye. A part sized on the size axis is a filled shape and takes the ground; a part sized on weight is a stroke, nothing is drawn on it, and it keeps the contrast-led mark. That is why progress-indicator's node fills and its connecting line does not, and why every border in the set still reads the mark.
Three checks had to learn the new shape, and all three are independent of the generator, which is why they caught it: the token verifier rebuilds the expected token list from config and rejected tokens it did not know; the React discipline test counts per-instance lookups and knew only two shapes, variant by level and level alone, so the variant-only shape is now spelled out beside them; and the fixtures embed the tokens, so they are regenerated before any baseline is recorded.
field-night's three bold roles all resolve to the same colour, as its three status roles already did. That is ADR-0006 working: in that theme meaning is carried by shape, position, weight and pattern, and the palette is where the claim is tested rather than asserted.
#Alternatives
Raise chroma and keep one role. Measured first and it is the 4% above. The single token cannot be both contrast led and saturation led, so tuning the curve under it only moves both jobs together.
Move the single token down the ramp. Makes fills vivid and text illegible. It is the same tradeoff with the other side losing.
P3 only, no sRGB echo. Simpler to generate, but the budget would stop measuring what people on ordinary displays actually see, which is the one thing this system exists to do.