Design tokens in plain CSS: a small system that scales
Design tokens are named values for the decisions in a design: colours, type sizes, spacing, radii, shadows, durations. Large teams store them in JSON and generate code for several platforms. For a website, plain CSS custom properties do the same job with no build step.
Two layers
I split tokens into two layers.
Primitive tokens describe what a value is. They’re the raw palette and scales:
:root {
--green-700: #047857;
--grey-50: #f5f7f6;
--grey-900: #141918;
--size-1: 0.25rem;
--size-4: 1rem;
--size-8: 2rem;
}
Semantic tokens describe what a value is for, and point at primitives:
:root {
--color-accent: var(--green-700);
--color-background: var(--grey-50);
--space-md: var(--size-4);
}
Components only use semantic tokens. That one rule is what makes theming possible: change --color-accent and every button, link and focus ring follows, without touching component CSS. It’s also how dark mode becomes a small change instead of a rewrite.
Name by role, not by look
--color-accent survives a rebrand; --color-green doesn’t. Good role names I reuse across projects:
--color-background,--color-surface,--color-text,--color-muted,--color-heading--color-accent,--color-accent-hover,--color-on-accent(text on accent backgrounds)--color-border,--color-focus
Fluid type and spacing with clamp()
Instead of changing font sizes at breakpoints, let them scale between a minimum and maximum:
:root {
--text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
--text-display: clamp(2.75rem, 1.5rem + 5vw, 5.5rem);
--space-section: clamp(4rem, 3rem + 5vw, 8rem);
}
Always include a rem part in the middle value, not only vw. A purely viewport-based size ignores the visitor’s browser font size setting, which is an accessibility failure.
Keep the scale short
A design usually needs five or six spacing steps and six to eight type sizes. When I turn a Figma file into code, I list every value the design uses and round them onto a scale. Values that are 2px off are almost always accidents.
Themes are just token overrides
Once components use semantic tokens, a theme is a block that reassigns them:
[data-theme="ocean"] {
--color-accent: #0b7194;
--color-accent-hover: #095d7a;
}
This site has nine colour themes built exactly this way. The colour values are generated in PHP and checked for WCAG contrast before they’re printed, so a theme can’t ship unreadable text.
Where tokens live
One file, loaded first: tokens.css. Everything else reads from it. When someone asks “what blue is that?”, there’s exactly one place to look.