Overview

@theme replaces the JavaScript theme object: each declaration becomes a CSS variable on :root and, when its name matches a namespace, a utility class. The hub is tailwind; the variable mechanics are in css-custom-properties.

Declare design tokens in @theme

Use @theme for any value that should map to a utility, and plain :root for variables that should not.

@import "tailwindcss";
 
@theme {
  --color-brand: oklch(0.72 0.18 264);
  --font-display: "Schibsted Grotesk", system-ui, sans-serif;
  --radius-card: 0.75rem;
  --shadow-card: 0 1px 3px rgb(0 0 0 / 0.1);
  --breakpoint-3xl: 120rem;
}

The namespace picks the utility: --color-* gives bg-brand, --font-* gives font-display, --radius-* gives rounded-card, --shadow-* gives shadow-card, --text-* gives font sizes, --breakpoint-* gives variants, --spacing is the base unit for px-4 and friends. Declaring the same tokens in @layer base generates no utilities.

@theme must be top level. Nesting it in a selector or media query is invalid.

Choose inline, static, or initial deliberately

  • @theme inline: use when a token points at another variable, for example --color-background: var(--background). Without inline, the utility references the theme variable and can resolve to an unexpected value.
  • @theme static: Tailwind emits only the variables your code uses; static forces all of them out, for values read only from JavaScript.
  • --color-*: initial;: clears a whole default namespace so only your values generate utilities. --*: initial; clears the entire default theme.

Define colors in oklch

Write color tokens as oklch(lightness chroma hue); the v4 default palette is oklch too. Lightness steps look even across hues, so a dark-mode counterpart is a lightness change. oklch() has been Baseline widely available since November 2025. Keep scales short (three to five steps per hue plus semantic names such as danger); every token is something to maintain.

Read tokens from plain CSS

Because tokens are variables, bespoke CSS reads them directly, which keeps CMS markup and third-party widgets on the design system.

.prose blockquote {
  border-left: 4px solid var(--color-brand);
  margin-block: --spacing(4);
  color: --alpha(var(--color-ink) / 70%);
}

--spacing() and --alpha() are Tailwind functions; the old theme() function is deprecated.

Switch themes by overriding the variable

Utilities read var(--color-surface), so overriding the variable on a scope restyles everything under it with no rebuild.

@theme {
  --color-surface: oklch(0.98 0 0);
  --color-ink: oklch(0.2 0 0);
}
 
[data-theme="dark"] {
  --color-surface: oklch(0.12 0 0);
  --color-ink: oklch(0.92 0 0);
}

For the toggle script and the dark: variant, see tailwind-dark-mode.

Share tokens across projects

Put tokens in their own CSS file and import it after Tailwind: @import "../brand/theme.css";. In a monorepo this file can be a package; no config merging is involved.

Avoid token drift

Two tokens for the same color (--color-blue-600 and --color-primary) split a design system. Search @theme for duplicates when adding tokens, and name tokens by role (--color-surface) at the component layer.