Design tokens are the named base values of your design: colors, spacing, font sizes and radii. In Tailwind CSS you can store them as CSS variables and use them directly as utility classes. This guide shows how to anchor tokens cleanly so your theme stays controllable in one place.
- Tokens are named decisions. Not blue, but primary.
- In Tailwind v4 they live as CSS variables in the theme and therefore apply to utilities and your own CSS at once.
- Few roles beat many values. Three greys you understand are better than twelve you guess at.
- With tokens a dark mode is a swap of values, not a second set of classes.
Why tokens instead of fixed values
When color values and spacing are hard-wired throughout the project, every adjustment turns into a search. A token gathers the value under a meaningful name, such as color-primary or space-4. A change at the source then takes effect everywhere.
Names also create a shared language. Design and development talk about primary and surface instead of random hex numbers. That reduces misunderstandings and keeps the interface consistent across many components.
Define tokens as CSS variables
The natural place for tokens are CSS variables in the :root block of your stylesheet file. There you set values like --color-primary, --radius-md or --space-4. Because they are real CSS variables, they are available everywhere and can be changed at runtime.
Keep the names flat and predictable. A scale like --space-1 to --space-8 or --color-fg and --color-bg is easier to remember than made-up labels. This order pays off as soon as the project grows.
:root { --blue-600: #1f5eff; --slate-900: #0f172a; --color-primary: var(--blue-600); --color-text: var(--slate-900); --radius-md: 8px;}
Make tokens available in Tailwind
In Tailwind CSS v4 you configure the theme directly in the stylesheet through the theme block. There you point at your variables, for example color-primary to var(--color-primary). From this entry Tailwind automatically generates fitting utilities like bg-primary or text-primary.
This keeps a single source of truth. Your components use the utility classes, these reach for the tokens and the tokens live centrally in the variables. Change a value there and the adjustment carries through all classes.
From the variable to the class in the component. Only the first station is touched when something changes.
@theme { --color-primary: var(--color-primary); --color-text: var(--color-text);} /* daraus entstehen bg-primary, text-primary, border-primary */
Semantic instead of raw names
It is worth separating two layers. On the lower layer are raw values like blue-500 with their color value. On the upper layer are semantic tokens like primary that point to a raw value. Components use only the semantic layer.
This small detour makes your system flexible. Want to change the brand color, you only change what primary points to. No part has to be touched, because none is bound directly to a raw value.
- primaryPrimary#1f5eff
- surfaceSurface#f8fafc
- textText#0f172a
- borderBorder#e2e8f0
Left, the values a mid-sized site produces. Right, what belongs in a project as a role.
- blue-500, blue-600, blue-light.
- After a rebrand, green is suddenly called blue-500.
- Nobody knows which of the two greys is for borders.
- Dark mode needs a second set of names.
- primary, surface, border, text.
- A rebrand only changes what primary points at.
- border is border, whatever grey sits behind it.
- Dark mode overrides the same four names.
Few roles beat many values.
A list of hex numbers is an inventory. It becomes a system only once every value has a job.
Switch themes through tokens
Because tokens are CSS variables, a dark mode becomes almost incidental. You override the same variables in a second context, for example under a data-theme attribute or a class on the root element. The utility classes stay unchanged.
The components know nothing about the active theme, they simply read the current variable values. That way you switch between light and dark without touching a single class in the parts.
:root { --color-surface: #ffffff; --color-text: #0f172a; } [data-theme="dark"] { --color-surface: #0f1115; --color-text: #f4f5f7; }
Maintain and document tokens
A token set is only as good as its upkeep. Keep the list short and remove values no one uses. Every extra token without a clear purpose makes the system heavier rather than richer.
A brief documentation helps the team. A section in the README or a DESIGN.md that lists colors, spacing and their meaning ensures new parts use the same values and the design stays consistent.
Why token sets fail
A set of tokens is quickly created. Making it still true a year later is the actual work.
- Too many values. Twelve greys mean nobody knows which one is for what any more. Three with a job cover almost everything.
- Names that name the color. blue-500 does not survive a rebrand. Primary, surface, border and text do.
- Exceptions inside the component. A single hard coded #1f2937 is the spot where a dark mode breaks later.
A token is a decision with a name. Without the name it is just a value.
Dark mode without a second set of classes
When colors live as tokens, a dark mode is a swap of values. You define the same names again under a selector, and everything using those names follows.
Two things you have to check yourself: images often need less contrast in the dark, and hard coded colors in individual components only show up there. Anyone working through tokens from the start has almost nothing to do when switching.




