# How to Migrate from Tailwind v3 to v4 Using the design.md Export Command

> Easily migrate from Tailwind v3 to v4. Use the design export command to generate Tailwind v4 CSS theme files and streamline your upgrade process.

- Repository: [Google Labs Code/design.md](https://github.com/google-labs-code/design.md)
- Tags: migration-guide
- Published: 2026-06-28

---

**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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/design.md) export command, you select your target Tailwind version through specific format identifiers.

**Tailwind v3 (JSON Theme Extension):**
- Format flag: `json-tailwind` (or alias `tailwind`)
- Output: JavaScript/JSON object for `theme.extend`
- Implementation: [`packages/cli/src/commands/export.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/commands/export.ts)

**Tailwind v4 (CSS @theme):**
- Format flag: `css-tailwind`
- Output: CSS `@layer` block with custom properties
- Implementation: [`packages/cli/src/linter/tailwind/v4/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/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:

```bash
design export path/to/DESIGN.md --format json-tailwind > tailwind-v3.json

```

This generates a JSON structure suitable for extending your [`tailwind.config.js`](https://github.com/google-labs-code/design.md/blob/main/tailwind.config.js) theme object.

### Switching to the v4 Format

To migrate, simply change the format flag:

```bash
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`](https://github.com/google-labs-code/design.md/blob/main/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:

```css
@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:

```js
// 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`](https://github.com/google-labs-code/design.md/blob/main/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:

```json
{
  "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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/tailwind/v4/serialize.ts), which prints the final CSS `@layer` block.

## Complete Code Examples

### Full Migration Workflow

```bash

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

```bash

# 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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/tailwind/v4/serialize.ts), which handles the final CSS serialization via the `serializeTailwindV4` function.