facebook / facebook/astryx

Overriding one side of a colour pair silently voids the contrast guarantee

Open
#5,014 1 comment 0 reactions 2 assignees Claimed by @rubyycheung View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.