Skip to content
← Back to blog
CSS

Design tokens in plain CSS

“Design tokens” sounded enterprise-heavy — monorepo, sync job, calendar invite called color council. At heart it is a named decision you do not want to renegotiate every afternoon.

In the browser, custom properties are the native place to store those decisions where CSS and your future self can find them. No ceremony required.

Start boring, stay boring

A small :root block with surface, text, border, and accent variables goes far. Override under [data-theme='dark'] or a class on html the way ChromaXP does, and you have theming without a framework. Name variables for role — what job does this color do? — not value. Role names survive brand refreshes; raw hex comments do not.

:root {
  --surface: #ffffff;
  --text: #0f172a;
  --border: #e2e8f0;
  --accent: #7c3aed;
}
[data-theme='dark'] {
  --surface: #0f172a;
  --text: #f1f5f9;
  --border: #334155;
  --accent: #a78bfa;
}

Bridge to Tailwind and Android

Mobile still wants colors.xml; Tailwind wants keys under theme.extend.colors. The numbers should match. The CSS variables generator, Tailwind config generator, and Android colors.xml generator share ramp logic, including OKLCH paths that map to gamut-safe HEX where resources do not speak OKLCH yet. Generate once, paste each platform, review behavior instead of hexadecimal drift.

Comments as documentation

Export “HEX plus OKLCH comment” so the next editor knows the perceptual intent behind a swatch — not just the six characters.

Good tokens encode intent

Great tokens make that intent readable in the file. That matters the moment someone lightens a step by hand and wonders why everything looks acidic.