Overriding one side of a colour pair silently voids the contrast guarantee
- Dominant language
- TypeScript
- Stars
- 13.1k
- Forks
- 1.1k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 687
Description
`expandColorScale` guarantees text at ≥4.5:1 and `--color-border-emphasized` at ≥3:1 against the surfaces it generates, for any accent. The guarantee is real — and it is silently void for any pair where the author writes one side by hand in `tokens`. Nothing warns, and the loss is invisible until someone measures the rendered pixels.
Measured on six independently authored themes (same brief set, two authoring aids):
**A theme that hand-tuned its accent for dark mode:**
```ts
color: {accent: '#0064E0', neutralStyle: 'warm', contrast: 'high'},
tokens: {
'--color-accent': ['#0064E0', '#1E6FE0'], // no --color-on-accent
}
```
The scale generated `--color-on-accent: #002D80` for the *original* accent. The rendered primary button in dark mode:
| | light | dark |
|---|---|---|
| this theme | 5.39:1 | **2.61:1** |
| a sibling theme that changed nothing | 6.46:1 | 7.34:1 |
Read off the real DOM (`getComputedStyle` on `core-button--primary`), not off the tokens. The author had run their own contrast math on the accent — against white, which is not what the theme renders. This one was a medication-administration UI, and the fix is one token.
**Same pattern on borders:** all three themes that hand-wrote `--color-border-emphasized` landed below 3:1 (1.46, 1.58, 1.66–1.86); the themes that left neutrals to `color: {}` stayed above it.
Options, roughly in order of how much I like them:
1. **Warn at build.** `astryx theme build` already resolves every token for both modes. It could check the pairs it knows about — `on-accent`/`accent`, `on-error`/`error`, `on-warning`/`warning`, `on-success`/`success`, text/surface, `border-emphasized`/surface — and warn below threshold. It knows which side was authored, so the message can name the missing counterpart.
2. **Derive the `on-*` token** from an authored base colour instead of leaving the generated one behind, unless it too was authored.
3. **Document it** where authors are looking: the `color` config docstring says what it generates but not that overriding one side of a pair drops the guarantee.
(1) and (3) at minimum. Happy to take (1) if there is agreement on the pair list and on warn-vs-error.
Contributor guide
Assessment
This issue has not been assessed yet.