Design system/Foundations
Color
Eighteen color tokens, each with a light value and a dark value. No component hard-codes a color; they all read from these tokens, and switching themes just means these variables change value.
In codeapp/globals.css
Tokens in both themes
Each swatch has two halves: light and dark. Tap a token name to copy it. The dark theme is turned on with data-theme="dark" on html, and a script reads it from the saved preference before hydration, so the page never flashes light.
Surface and background
#F6F5F3Dark#151311#FFFFFFDark#1E1C19#F4F3F1Dark#262320#ECEAE7Dark#2A2723#E2E0DCDark#383430Text
#21201EDark#EDEAE5#716D67Dark#9C968E#B3B0AADark#6B665FAction and emphasis
#34312DDark#EDEAE5#211F1CDark#FFFFFF#FFFFFFDark#1B1916#F04D1ADark#F4571F#C8390BDark#C8390B#C43A0FDark#FF7040#FDEEE8Darkrgba(240,77,26,.14)#DEDCD8Dark#3B3833Status
#137A40Dark#43B374#E9F6EEDarkrgba(46,160,96,.14)#CC3424Dark#E05747#CC3424Dark#C4301F#FDEEECDarkrgba(221,59,42,.15).my-surface {
background: var(--card);
border: 1px solid var(--border);
color: var(--text);
}
/* Tailwind shorthand */
<p className="text-(--text-secondary)" />Charcoal, not black
The primary button and active pill are charcoal (--dark), not orange. In dark mode the same token turns light and the text on it (--dark-contrast) turns dark, so the primary button has the highest contrast on the page in both themes without spending the brand color.
Status colors
Green for done and success; red for errors, delete, and overdue. Each has a soft version for backgrounds. In dark mode the soft versions are translucent so they sit on any surface.
Rules
Do
One strong orange element per page: the active state or one important button.
Don't
Several orange buttons side by side; none of them is more important anymore.
Do
var(--card) for a new surface and var(--border) for its edge.
Don't
#fff or a hand-picked gray; in dark mode it stays a white rectangle.
Do
For a mixed color, use color-mix with a token, like 10 percent orange on transparent.
Don't
A new hex value that only works in the light theme.
Color pairs and their contrast
Every combination is measured in both themes. The dark soft colors are semi-transparent, so their ratios are calculated on --card: dark --accent-soft on a card becomes #3B2319.
14.94 · 15.45AAA in both12.94 · 14.62--dark-contrast on --dark; AAA5.14 · 5.80AA in both2.16 · 2.99Fails; not needed for text5.19 · 5.19--accent-strong; btn-accent and badge-accent5.30 · 6.19--accent-text; AA3.63 · 3.37Avatars and icons only; not labels3.21 · 4.32The “Review” status badge4.86 · 5.38AA5.14 · 4.55AA in both5.14 · 5.54--red-strong; btn-danger and badge-count4.64 · 5.33.pill and .badge-soft; AA1.37 · 1.46Non-text, below 3Do
Text that needs to be read uses --text.
Don't
Essential descriptions in --text-tertiary; in the light theme it's 2.16 to 1.
Do
Say the status in words too: “Done” next to the green dot.
Don't
Just a green or red dot.
Do
Define a new color in both :root and html[data-theme="dark"].
Don't
A token with only a light value; in dark it stays light.
Related pages