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:
- Tailwind v3 →
TailwindEmitterHandlerinpackages/cli/src/linter/tailwind/handler.ts - Tailwind v4 →
TailwindV4EmitterHandlerinpackages/cli/src/linter/tailwind/v4/handler.ts - W3C DTCG →
DTCGEmitterHandler(located alongside other emitters in the linter directory)
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:
mapColorsgenerates{ name: hex }objects for color tokensmapFontFamiliesproduces{ name: [family] }arrays for typographymapDimensionsanddimToStringhandle 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:
0indicates successful export1signals an invalid--formatargument or emitter error2indicates 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.
Recommended Usage Patterns
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
lintbeforeexportto catch token errors early and enforce quality gates. - Choose the appropriate format (
json-tailwind,css-tailwind, ordtcg) 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.tsfor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →