How to Migrate from Tailwind v3 to v4 Using the design.md Export Command
Run design export path/to/DESIGN.md --format css-tailwind to generate a Tailwind v4-compatible CSS theme file instead of the JSON format used for v3.
The google-labs-code/design.md CLI provides a streamlined migration path between Tailwind CSS versions through its export command. By changing a single --format flag, you can convert your DESIGN.md token definitions from the v3 theme-extend JSON structure to the v4 CSS @theme syntax. This guide covers the exact CLI usage, validation rules, and integration steps required for a seamless upgrade.
Understanding the Export Format Differences
When working with the design.md export command, you select your target Tailwind version through specific format identifiers.
Tailwind v3 (JSON Theme Extension):
- Format flag:
json-tailwind(or aliastailwind) - Output: JavaScript/JSON object for
theme.extend - Implementation:
packages/cli/src/commands/export.ts
Tailwind v4 (CSS @theme):
- Format flag:
css-tailwind - Output: CSS
@layerblock with custom properties - Implementation:
packages/cli/src/linter/tailwind/v4/handler.ts
The v4 emitter, defined in the TailwindV4EmitterHandler class, performs strict validation that the v3 exporter does not require, ensuring compatibility with Tailwind v4's CSS-first configuration approach.
Step-by-Step Migration Process
Exporting for Tailwind v3 (Baseline)
First, confirm your current v3 setup works:
design export path/to/DESIGN.md --format json-tailwind > tailwind-v3.json
This generates a JSON structure suitable for extending your tailwind.config.js theme object.
Switching to the v4 Format
To migrate, simply change the format flag:
design export path/to/DESIGN.md --format css-tailwind > tailwind-v4.css
The CLI validates the --format argument against a closed enum (FORMATS) in packages/cli/src/commands/export.ts. Supplying css-tailwind triggers the TailwindV4EmitterHandler, which transforms your design tokens into CSS custom properties.
Integrating the Generated CSS
The output file contains a CSS @layer block:
@layer theme {
--color-primary: #ff6600;
--font-family-sans: "Inter", "system-ui", "sans-serif";
--spacing-1: 0.25rem;
}
You can import this directly into your Tailwind configuration:
// tailwind.config.cjs
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx,html}'],
corePlugins: { preflight: false },
plugins: [
require('@tailwindcss/forms'),
// Load the generated CSS @theme
require('tailwindcss/plugin')(function({ addBase }) {
const css = require('fs').readFileSync('tailwind-v4.css', 'utf8');
addBase(css);
}),
],
};
Token Validation and Naming Requirements
Tailwind v4 imposes stricter naming conventions than v3. According to the source code in packages/cli/src/linter/tailwind/v4/handler.ts (lines 37-44), the v4 emitter validates every token name against the CSS identifier regex ^[a-zA-Z0-9][a-zA-Z0-9-]*$.
If a token fails validation, the CLI aborts with an INVALID_TOKEN_NAME error and exits with code 1, making the migration step CI-friendly.
Fixing Invalid Token Names
If you encounter a validation error:
{
"error": "Token name \"my token\" is not a valid CSS identifier for Tailwind v4 export (must match /^[a-zA-Z0-9][a-zA-Z0-9-]*$/)."
}
Rename the token in your DESIGN.md file to match the pattern (e.g., replace spaces with hyphens) and re-run the export command.
Data Mapping Reference
The TailwindV4EmitterHandler maps DESIGN.md tokens to specific CSS variable groups through the TailwindV4ThemeData schema defined in packages/cli/src/linter/tailwind/v4/spec.ts:
- Colors →
theme.colors(hex strings) - Typography → Sibling objects including
fontFamily,fontSize,lineHeight,letterSpacing, andfontWeight(font-family strings are escaped as CSS literals viacssStringLiteral) - Rounded & Spacing →
theme.borderRadiusandtheme.spacing, generated by themapDimensionsfunction
The resulting theme data is handed to serializeTailwindV4 in packages/cli/src/linter/tailwind/v4/serialize.ts, which prints the final CSS @layer block.
Complete Code Examples
Full Migration Workflow
# Step 1: Export to v4 format
design export ./DESIGN.md --format css-tailwind > ./styles/tailwind-theme.css
# Step 2: Import in your CSS or config
# In your main CSS file:
@import './styles/tailwind-theme.css';
Handling Validation Errors
If the export fails due to invalid token names:
# Attempt export
design export ./DESIGN.md --format css-tailwind > theme.css
# Error: Token name "primary blue" is not valid...
# Fix in DESIGN.md, then retry
design export ./DESIGN.md --format css-tailwind > theme.css
Summary
- Use
--format css-tailwindinstead ofjson-tailwindto generate Tailwind v4 compatible output according to thegoogle-labs-code/design.mdsource code. - The v4 emitter validates token names against the regex
^[a-zA-Z0-9][a-zA-Z0-9-]*$and exits with code 1 on validation failures. - Colors, typography, and spacing map to CSS custom properties in an
@layerblock viaserializeTailwindV4. - Reference
packages/cli/src/linter/tailwind/v4/handler.tsfor the exact implementation of theTailwindV4EmitterHandlerclass.
Frequently Asked Questions
What is the difference between json-tailwind and css-tailwind formats?
The json-tailwind format generates a JSON object for Tailwind v3's theme.extend configuration, while css-tailwind produces a CSS @layer block containing CSS custom properties compatible with Tailwind v4's CSS-first configuration approach. The CLI selects the appropriate emitter based on the format flag in packages/cli/src/commands/export.ts.
Why does the v4 export fail with an invalid token name error?
Tailwind v4 requires CSS-valid identifiers for theme tokens. The emitter checks all token names against the regex ^[a-zA-Z0-9][a-zA-Z0-9-]*$ in packages/cli/src/linter/tailwind/v4/handler.ts and aborts with an INVALID_TOKEN_NAME error if any token contains spaces or special characters. Rename tokens to use hyphens instead of spaces to resolve this.
Can I use both v3 and v4 exports in the same project?
Yes, you can generate both formats from the same DESIGN.md file by running the export command twice with different --format flags. This is useful for gradual migration or supporting legacy components while upgrading to v4, as both json-tailwind and css-tailwind formats can coexist in your build pipeline.
Where is the v4 export logic implemented in the source code?
The v4 export functionality is implemented in packages/cli/src/linter/tailwind/v4/handler.ts, which validates tokens and builds the theme object, and packages/cli/src/linter/tailwind/v4/serialize.ts, which handles the final CSS serialization via the serializeTailwindV4 function.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →