Skip to content
Ramesh Gautam

Design tokens in plain CSS: a small system that scales

Design tokens in plain CSS: a small system that scales, a Frontend article by Ramesh Gautam

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.