DESIGN.md to Tailwind v4 Theme
On this page
The @google/design.md CLI ships a css-tailwind export target that emits a Tailwind v4 @theme block with CSS custom properties for every token in your DESIGN.md file. Paste the block into your globals.css, add @import "tailwindcss", and every --color-*, --spacing-*, and --radius-* variable becomes a Tailwind utility immediately.
Background: Tailwind v4 and the @theme model#
Tailwind v4 moved away from the JavaScript tailwind.config.js theme object and adopted a CSS-first configuration model. Themes are now defined directly in CSS using a special @theme rule:
@import "tailwindcss";
@theme {
--color-primary: #0F172A;
--color-secondary: #475569;
--color-tertiary: #3B82F6;
--spacing-sm: 8px;
--spacing-md: 16px;
--radius-md: 8px;
}
Any CSS custom property defined inside @theme is registered with Tailwind's engine. The prefix determines the utility family: --color-* produces bg-*, text-*, border-*, and so on; --spacing-* feeds the spacing scale; --radius-* feeds rounded-*.
This is the model used by this site. The app/globals.css file defines @theme inline { --color-canvas: var(--canvas); ... } to expose the chrome palette to Tailwind utilities without duplicating the values.
The css-tailwind export target#
The @google/design.md CLI has four export formats. The one you want for Tailwind v4 is css-tailwind:
| Format | What it emits |
|---|---|
css-tailwind | Tailwind v4 @theme block with CSS custom properties |
json-tailwind | Tailwind v3 theme.extend JSON object |
tailwind | Alias for json-tailwind |
dtcg | W3C Design Tokens Community Group JSON |
The css-tailwind target maps each DESIGN.md token section to a CSS variable family:
| DESIGN.md section | CSS variable prefix |
|---|---|
colors | --color- |
typography.fontFamily | --font- |
typography.fontSize | --text- |
typography.lineHeight | --leading- |
typography.letterSpacing | --tracking- |
typography.fontWeight | --font-weight- |
rounded | --radius- |
spacing | --spacing- |
Step 1: Write or lint your DESIGN.md#
Start from a valid DESIGN.md. If you do not have one yet, use the tool to build one interactively. To validate an existing file:
npx @google/design.md lint DESIGN.md
Exit code 0 means no errors (warnings are acceptable). If you see a broken-ref error, fix the unresolved reference before exporting. The orphaned-tokens rule fires only when your file includes a non-empty components section; without components it is skipped entirely.
A minimal DESIGN.md that covers all exported sections:
---
name: Slate UI
version: alpha
colors:
primary: "#0F172A"
secondary: "#475569"
tertiary: "#3B82F6"
neutral: "#F8FAFC"
surface: "#FFFFFF"
on-surface: "#0F172A"
error: "#EF4444"
typography:
headline-lg:
fontFamily: "Inter"
fontSize: 36px
fontWeight: 700
lineHeight: "1.1"
letterSpacing: "-0.02em"
body-md:
fontFamily: "Inter"
fontSize: 16px
fontWeight: 400
lineHeight: "1.65"
rounded:
sm: 4px
md: 8px
lg: 16px
spacing:
sm: 8px
md: 16px
lg: 32px
---
Note: lineHeight must be a quoted string (e.g. "1.1") for the parser to retain it. An unquoted YAML number (e.g. 1.1) is dropped and no --leading-* variable is emitted.
Step 2: Run the export#
npx @google/design.md export DESIGN.md --format css-tailwind
The CLI writes the @theme block to stdout. Redirect it to a file if you want to keep it separate:
npx @google/design.md export DESIGN.md --format css-tailwind > tokens.css
For the DESIGN.md above, the output looks like this:
@theme {
--color-primary: #0f172a;
--color-secondary: #475569;
--color-tertiary: #3b82f6;
--color-neutral: #f8fafc;
--color-surface: #ffffff;
--color-on-surface: #0f172a;
--color-error: #ef4444;
--font-headline-lg: "Inter";
--font-body-md: "Inter";
--text-headline-lg: 36px;
--text-body-md: 16px;
--leading-headline-lg: 1.1;
--leading-body-md: 1.65;
--tracking-headline-lg: -0.02em;
--font-weight-headline-lg: 700;
--font-weight-body-md: 400;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 32px;
}
Step 3: Add the block to your globals.css#
In a Tailwind v4 project, globals.css (or whichever file holds your @import "tailwindcss" statement) is where the @theme block lives. You have two options:
Option A: Paste directly into globals.css#
@import "tailwindcss";
@theme {
--color-primary: #0F172A;
--color-secondary: #475569;
--color-tertiary: #3B82F6;
--color-neutral: #F8FAFC;
--color-surface: #FFFFFF;
--color-on-surface: #0F172A;
--color-error: #EF4444;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 32px;
}
Option B: Keep tokens in a separate file and import it#
/* globals.css */
@import "tailwindcss";
@import "./tokens.css";
/* tokens.css (the css-tailwind export output) */
@theme {
--color-primary: #0F172A;
/* ... */
}
Both options work. Option B is easier to regenerate when the DESIGN.md changes.
Step 4: Use the tokens as Tailwind utilities#
Once the @theme block is in place, every registered variable becomes a utility class. Because Tailwind v4 derives utilities from the CSS variable names, you get the full utility set without any additional configuration:
<!-- color utilities -->
<h1 class="text-primary">Headline</h1>
<div class="bg-surface text-on-surface">Card content</div>
<button class="bg-tertiary text-white">Primary action</button>
<p class="text-error">Something went wrong.</p>
<!-- spacing and radius utilities -->
<div class="p-md rounded-lg">Padded card with large radius</div>
<section class="mt-lg">Spaced section</section>
The token names from DESIGN.md become the utility suffixes directly. --color-tertiary becomes bg-tertiary, text-tertiary, and border-tertiary. --radius-md becomes rounded-md.
Step 5: Re-export when your DESIGN.md changes#
Tokens and the Tailwind theme are now kept in sync through the CLI command. Set up a script in package.json to make regeneration one command:
{
"scripts": {
"tokens": "design.md export DESIGN.md --format css-tailwind > src/tokens.css"
}
}
Or if you use npx directly:
npx @google/design.md export DESIGN.md --format css-tailwind > src/tokens.css
The @theme inline variant#
Tailwind v4 also accepts @theme inline, which resolves the variables eagerly at build time rather than leaving them as runtime CSS variables. This is useful when you want to reference a :root custom property inside @theme rather than duplicating the value:
:root {
--brand-blue: #3B82F6;
}
@theme inline {
--color-tertiary: var(--brand-blue);
}
The css-tailwind export omits the inline keyword because it writes literal values, not references to other properties. If you want the inline variant (for example, to combine with a dark-mode :root block), add inline manually after pasting the output.
Up to the guide#
- CLI and Lint Rules Reference: all export formats and lint rules
- DESIGN.md Overview: the file format explained from first principles
- Colors reference: color token names, reference syntax, and contrast-ratio lint
- Design Tokens Explained: the three-tier model that DESIGN.md maps onto