Color
One brand color, one pop, and three signals. Every role has a light and a dark value, and every text pairing passes WCAG AA.
#Principles
--color-surface, never #ffffff. Raw values live only in tokens/.#The palette
Left half of each chip is light, right half is dark. Hex values are read from tokens/colors.json.
#Which color for what
| Need | Role | Example |
|---|---|---|
| Page background | bg | The paper behind everything |
| Cards, sidebar, inputs | surface with a line border | Card, tile, field |
| Wells and tracks | sunken | Inset, rank bar track, hover fill |
| Text and numbers | ink, secondary muted | Headlines, labels |
| Primary action, ready, positive change | accent; hover accent-strong; tint accent-soft | ● Connected |
| The latest, the new | pop, tint pop-soft | Latest |
| Needs a look, the weekly action | warn, tint warn-soft | Waiting for first report |
| Decreases, failures, destructive | danger, tint danger-soft | Failed |
| Keyboard focus | focus | 2px ring on every interactive element |
| Modal backdrop | overlay | Handoff |
Code
<div class="btn-row">
<span class="pill pill-accent">Ready</span>
<span class="pill pill-warn">Needs a look</span>
<span class="pill pill-danger">Failed</span>
<span class="pill pill-pop">Latest</span>
</div>
Do
- Use accent for one primary action per view
- Pair every signal color with words or an arrow
- Test in grayscale and in both themes
Don’t
- Use accent as a theme color or for nav chrome
- Use violet for status, or in more than two places
- Add a new color. Propose a role in
tokens/first - Use gradients, glows, or decorative color
#Light and dark
Dark is not an afterthought. Every role has a dark value. Dark follows the OS by default and can be forced with data-theme="dark" or "light" on <html> (Settings → Appearance). Any element can opt into dark with data-theme="dark"; that is how the specimens on this site are previewed.
A new dark value has to be added in both dark blocks of the tokens (the prefers-color-scheme block and the [data-theme='dark'] block). Email previews are the exception: real email is light in both themes, so they read the --color-mail-* tokens.
#Contrast
Every text pairing the product uses meets WCAG AA (4.5:1 or better) in both themes. Check any new pairing before you add it.
| Pairing | Light | Dark | ||
|---|---|---|---|---|
ink on bgBody text on the page |
Aa | 17.0:1 | Aa | 15.8:1 |
ink on surfaceText and numbers on cards |
Aa | 18.5:1 | Aa | 14.4:1 |
muted on surfaceSecondary text on cards |
Aa | 7.2:1 | Aa | 6.2:1 |
muted on bgSecondary text on the page |
Aa | 6.6:1 | Aa | 6.8:1 |
muted on sunkenLabels inside an inset |
Aa | 6.1:1 | Aa | 7.0:1 |
accent on surfaceLinks, positive change |
Aa | 6.3:1 | Aa | 7.3:1 |
accent on bgLinks on the page |
Aa | 5.8:1 | Aa | 8.0:1 |
accent on accent-softActive nav item, ready pill |
Aa | 5.4:1 | Aa | 6.3:1 |
accent-strong on surfaceHovered link |
Aa | 9.0:1 | Aa | 9.2:1 |
on-accent on accentPrimary button label |
Aa | 6.3:1 | Aa | 7.5:1 |
on-accent on accent-strongPrimary button label, hovered |
Aa | 9.0:1 | Aa | 9.5:1 |
warn on surfaceAttention text |
Aa | 6.5:1 | Aa | 8.4:1 |
warn on warn-softWeekly action band, waiting pill |
Aa | 5.6:1 | Aa | 7.1:1 |
danger on surfaceDecreases, errors |
Aa | 7.5:1 | Aa | 7.7:1 |
danger on danger-softDestructive hover, failed pill |
Aa | 6.2:1 | Aa | 6.7:1 |
pop on surface“Latest” text |
Aa | 6.4:1 | Aa | 7.1:1 |
pop on pop-softLatest pill |
Aa | 5.4:1 | Aa | 6.4:1 |
on-pop on popText on a solid pop fill |
Aa | 6.4:1 | Aa | 7.9:1 |
Computed from tokens/colors.json when the site is built. The build fails if any pairing drops below 4.5:1.