Best Practices for Using the Export Feature in Design.md: A Complete Guide

The export feature in Design.md converts token definitions from DESIGN.md files into Tailwind v3 JSON, Tailwind v4 CSS, or W3C DTCG format using pure emitter handlers located in the CLI package, enabling deterministic design system pipelines when integrated with linting and CI workflows.

The export feature in the google-labs-code/design.md repository provides a robust mechanism for transforming markdown-based design tokens into implementation-ready code. By leveraging side-effect-free emitter functions and standardized exit codes, this tool bridges the gap between design specifications and development workflows. Understanding the internal architecture ensures reliable automation and extensibility for custom design system needs.

Understanding the Export Architecture

The export pipeline follows a strict functional architecture that separates parsing, transformation, and serialization concerns. This design ensures testability and consistent behavior across different output formats.

CLI Entry Point and Emitter Selection

The export command begins at the CLI entry point in packages/cli/src/utils.ts, which handles generic I/O utilities and argument parsing. Based on the --format flag, the dispatcher routes to one of three specialized handlers:

Pure Mapping Functions

Each emitter receives a fully parsed DesignSystemState object and returns a plain JavaScript object through a pure execute(state) method. The TailwindEmitterHandler constructs the theme.extend structure by invoking specialized mapping functions:

  • mapColors generates { name: hex } objects for color tokens
  • mapFontFamilies produces { name: [family] } arrays for typography
  • mapDimensions and dimToString handle font sizes, border-radius, and spacing values

These functions are side-effect-free, making them easy to unit test and compose into custom pipelines.

Serialization and Exit-Codes

The raw emitter output passes through formatOutput in packages/cli/src/utils.ts, which serializes JSON with pretty-printing or generates CSS blocks. The CLI adheres to a strict exit-code contract for CI integration:

  • 0 indicates successful export
  • 1 signals an invalid --format argument or emitter error
  • 2 indicates the input DESIGN.md file cannot be read

Export Format Options

The export feature supports three primary output formats, each serving distinct integration scenarios.

json-tailwind (Alias: tailwind)

Generates a JSON object intended for theme.extend in Tailwind v3 configurations. This format produces a ready-to-paste fragment for tailwind.config.js, mapping tokens to Tailwind's theme structure.

css-tailwind

Outputs a CSS @theme { … } block using Tailwind v4 custom-property namespaces. This format creates a standalone CSS file that Tailwind v4 can consume directly without additional JavaScript configuration.

dtcg

Produces Design Token JSON conforming to the W3C Design Tokens Community Group (DTCG) specification. This format enables interoperability with other design-token tools and platforms that support the standardized schema.

One-Off Token Generation

For manual updates, pipe the output directly to a version-controlled file:

npx @google/design.md export --format json-tailwind DESIGN.md > tailwind.theme.json

Store the generated file in your repository to ensure reproducible builds across environments.

CI Lint-Then-Export Pipeline

Always run the linter before exporting to catch token errors early:

npx @google/design.md lint DESIGN.md && npx @google/design.md export --format css-tailwind DESIGN.md > theme.css

While the exporter succeeds even if linting finds warnings, CI jobs should enforce a clean lint pass to prevent invalid tokens from reaching production.

Programmatic Integration

Import the library directly for custom tooling:

import { exportDesign } from '@google/design.md/cli';
import { parse } from '@google/design.md/parser';

const { designSystem } = parse(markdown);
const result = exportDesign(designSystem, 'json-tailwind');

This approach uses the same pure handlers as the CLI while allowing custom pre-processing or post-processing of the DesignSystemState.

Debugging Output

Use the markdown format to inspect the emitter's raw object structure:

npx @google/design.md export --format markdown DESIGN.md

This human-readable dump helps verify token mappings before committing to a specific output format.

Implementation Examples

Exporting Tailwind v3 JSON from a Script

The following TypeScript example demonstrates programmatic usage without the CLI:

import { readFileSync } from 'node:fs';
import { parse } from '@google/design.md/parser';
import { TailwindEmitterHandler } from '@google/design.md/packages/cli/src/linter/tailwind/handler';
import { formatOutput } from '@google/design.md/packages/cli/src/utils';

// Load and parse DESIGN.md
const markdown = readFileSync('DESIGN.md', 'utf-8');
const { designSystem } = parse(markdown);

// Run the Tailwind v3 emitter
const emitter = new TailwindEmitterHandler();
const result = emitter.execute(designSystem);

// Serialize as pretty-printed JSON
const json = formatOutput(result.data, { format: 'json' });
console.log(json);

GitHub Actions CI Workflow

Automate token generation in a CI pipeline using the exit-code contract:

name: Export Design Tokens
on: [push]
jobs:
  export:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install dependencies
        run: npm ci
      - name: Lint DESIGN.md
        run: npx @google/design.md lint DESIGN.md
      - name: Export Tailwind v4 CSS
        run: |
          npx @google/design.md export --format css-tailwind DESIGN.md > theme.css
      - name: Upload artifact
        uses: actions/upload-artifact@v3
        with:
          name: tailwind-theme
          path: theme.css

The job fails automatically if either linting or exporting returns a non-zero exit code.

Creating a Custom Emitter

Extend the system by implementing the EmitterSpec interface:

// src/linter/custom/custom-emitter.ts
export class MyCustomEmitter implements EmitterSpec {
  execute(state: DesignSystemState) {
    // Example: flatten all token names into a CSV string
    const rows = Array.from(state.colors.entries())
      .map(([name, color]) => `${name},${color.hex}`);
    return { success: true, data: rows.join('\n') };
  }
}

Register the emitter in the CLI's format switch and follow the existing handler pattern of returning { success: true, data: ... } for consistency with TailwindEmitterHandler.

Summary

  • Run lint before export to catch token errors early and enforce quality gates.
  • Choose the appropriate format (json-tailwind, css-tailwind, or dtcg) based on your target platform and Tailwind version.
  • Use exit codes (0, 1, 2) in CI scripts to automate quality checks and artifact generation.
  • Leverage pure functions in packages/cli/src/linter/tailwind/handler.ts for programmatic access and testing.
  • Extend via EmitterSpec by copying the pattern from existing handlers like TailwindV4EmitterHandler.

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 @theme block using Tailwind v4's custom-property syntax. Choose json-tailwind for JavaScript-based configuration files and css-tailwind for CSS-first Tailwind v4 setups.

How do I handle export failures in CI pipelines?

The CLI returns specific exit codes: 1 for invalid format arguments or emitter errors, and 2 for file read errors. Structure your CI scripts to fail fast on non-zero codes, and always run npx @google/design.md lint before exporting to catch token definition errors before they reach the export stage.

Can I use the export feature programmatically without the CLI?

Yes, import exportDesign from @google/design.md/cli or instantiate specific handlers like TailwindEmitterHandler directly from packages/cli/src/linter/tailwind/handler.ts. Both approaches accept a DesignSystemState object and return a structured result, allowing integration into custom build tools or preprocessors.

Where are the emitter implementations located in the source code?

The Tailwind v3 emitter resides in packages/cli/src/linter/tailwind/handler.ts as the TailwindEmitterHandler class, while the Tailwind v4 implementation is in packages/cli/src/linter/tailwind/v4/handler.ts as TailwindV4EmitterHandler. The DTCG emitter follows the same pattern in the adjacent directory structure. All emitters implement the EmitterSpec interface with a pure execute(state) method.

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 →