Overview

@theme is the v4 replacement for the JavaScript config object. Every declaration becomes a CSS custom property at :root and a generated utility class. A single source of truth drives both Tailwind utilities and bespoke CSS. The parent reference for Tailwind v4 conventions is tailwind; for the CSS custom property substrate see css-custom-properties.

Declare tokens in @theme, not in raw @layer base

Place design tokens inside the @theme block at the top of your entry CSS file. Tailwind registers them as utilities automatically.

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

This produces bg-brand, text-ink, bg-surface, font-display, rounded-card, and shadow-card without any extra configuration. Putting the same properties in @layer base skips the utility generation step and is a common migration mistake from v3.

Use oklch for color tokens

Define color tokens with oklch(lightness chroma hue) instead of hex or rgb. The oklch color space is perceptually uniform: stepping lightness by 10% looks like the same visual change across every hue. Use css-custom-properties patterns for fallback values if you need to support older browsers, though evergreen browsers have shipped oklch since 2023.

@theme {
  --color-brand-100: oklch(0.95 0.05 264);
  --color-brand-500: oklch(0.72 0.18 264);
  --color-brand-900: oklch(0.35 0.14 264);
}

The scale becomes bg-brand-100, bg-brand-500, and bg-brand-900. Keep scales short. A full 50 to 950 scale for every hue multiplies your token count and your build output.

Reference tokens from bespoke CSS without duplication

Because @theme writes every token to :root as a CSS custom property, bespoke CSS reads them directly.

.prose blockquote {
  border-left: 4px solid var(--color-brand-500);
  padding-inline-start: 1rem;
  color: var(--color-ink);
}

This means third-party markup you cannot annotate with classes, like a CMS article body, still participates in the design system. There is no secondary token definition file.

Keep the token set small and intentional

Every token in @theme generates a utility. Fifty color tokens times five utility families (bg, text, border, ring, outline) is 250 utility classes in the output. Remove unused tokens to keep the build small; the scanner only prunes utilities that never appear in source, not tokens that appear in @theme but only via var() in bespoke CSS.

Rules of thumb for a tight token set:

  • Colors: brand scale (3 to 5 steps), neutral scale (3 to 5 steps), semantic names (danger, success, warning).
  • Spacing: override the default scale only if the design uses a non-standard base unit.
  • Typography: one display font if needed; body font only if the default stack is not right.
  • Radius: card, button, input. Three values cover most UIs.

Support runtime theme switching with CSS custom property overrides

@theme writes to :root. Override the same property on a scoped selector or in a media query to switch themes at runtime without a build step.

@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);
}

A JavaScript one-liner toggles data-theme="dark" on the root element. All Tailwind utilities that use the token (bg-surface, text-ink) update immediately. No class rewriting or style recalculation beyond what the browser’s cascade already does. For the full dark mode strategy, see tailwind-dark-mode.

Bridge the design system from Figma token exports

Design tools like Figma export tokens as JSON or CSS variables. Map the export to @theme in a single translation file rather than scattering raw values through components.

/* tokens.css — generated from Figma, committed to version control */
@theme {
  --color-primary: oklch(0.72 0.18 264);
  --color-secondary: oklch(0.68 0.15 190);
  --space-unit: 0.25rem;
  --radius-base: 0.375rem;
}

Import this file before your component CSS. When the design system updates, only tokens.css changes; no component files need touching.

Avoid token drift with a quarterly audit

Token drift is the most common long-term failure mode: two tokens for the same color, a spacing token that nothing uses, a font token that duplicates the default. Run a search for -- in your entry CSS file quarterly and delete anything with no non-@theme references. The build will not catch this for you.