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):

Tailwind v4 (CSS @theme):

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, and fontWeight (font-family strings are escaped as CSS literals via cssStringLiteral)
  • Rounded & Spacing → theme.borderRadius and theme.spacing, generated by the mapDimensions function

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-tailwind instead of json-tailwind to generate Tailwind v4 compatible output according to the google-labs-code/design.md source 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 @layer block via serializeTailwindV4.
  • Reference packages/cli/src/linter/tailwind/v4/handler.ts for the exact implementation of the TailwindV4EmitterHandler class.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →