Skip to content
Get started
Back to blogEngineering

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.

RMRavi MenonApr 30, 20261 min read

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.

#tailwind#css#theming