Theming with design tokens in Tailwind CSS v4
One file of semantic variables can drive your entire palette — including a flawless, no-flash dark mode. Here is the pattern StellarGrid uses.
Hard-coding bg-slate-900 across two hundred elements is how themes become impossible to rebrand. The fix is semantic design tokens: you name colours by their role, not their value.
Roles, not values
Instead of text-slate-100, you write text-fg. Instead of bg-slate-950, you write bg-bg. The actual colour lives in one place:
:root {
--bg: #050614;
--fg: #e9ebfb;
--brand: #8b95ff;
}
Rebranding becomes a five-minute job: change a handful of variables and the entire site re-colours.
Wiring tokens into Tailwind v4
Tailwind v4 makes this beautifully direct with @theme inline:
@theme inline {
--color-bg: var(--bg);
--color-fg: var(--fg);
--color-brand: var(--brand);
}
Now bg-bg, text-fg and text-brand are real utilities — and because they point at CSS variables, they update instantly when those variables change.
Dark mode with no flash
The trick to a flicker-free dark mode is to set the theme before the first paint, with a tiny inline script in the <head>:
<script is:inline>
const t = localStorage.getItem('theme') ?? 'dark';
document.documentElement.classList.toggle('light', t === 'light');
</script>
Then your light theme is just another set of variable values:
:root.light {
--bg: #ffffff;
--fg: #0b1020;
}
Define colour once, by meaning. Everything else falls into place.
That is the whole system. No theme provider, no context, no flash — just variables doing what variables do best.