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). Withoutinline, the utility references the theme variable and can resolve to an unexpected value.@theme static: Tailwind emits only the variables your code uses;staticforces 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.