How Design.md Handles Different Data Structures for Export
The Design.md CLI employs a strategy pattern where format-specific emitter classes transform a common DesignSystemState model into distinct output structures—including Tailwind JSON, CSS custom properties, and DTCG-compliant JSON—through a pure, side-effect-free pipeline.
The Design.md project from Google Labs provides a CLI tool that converts design-system tokens defined in DESIGN.md files into production-ready code. To handle different data structures for export, the architecture cleanly separates token modeling from format-specific serialization, enabling developers to add new output targets without modifying core logic.
How the Export Pipeline Handles Different Data Structures
The export system orchestrates the conversion process through three distinct layers defined in packages/cli/src/commands/export.ts and supporting files.
CLI Command Dispatch
The entry point validates the --format argument against a closed FORMATS enum, runs the linter to obtain a DesignSystemState, and dispatches to the appropriate handler. This ensures only supported data structures are generated and provides a unified interface for all export operations.
The Format-Agnostic Model
At the core lies the DesignSystemState model defined in packages/cli/src/linter/model/spec.ts. This pure data structure stores tokens as Map<string, …> objects (e.g., state.colors, state.typography, state.spacing), remaining completely agnostic to output formats. Because the model contains no side effects, emitters can focus exclusively on mapping these maps to their target structures.
Emitter Strategy Pattern
Each export format implements a specific emitter class conforming to the *EmitterSpec interface. These classes receive the DesignSystemState and return a standardized result object { success: boolean, data: … }. This pattern isolates format-specific logic—such as JSON schema construction or CSS string building—within dedicated handlers.
Supported Data Structures for Export
The CLI supports four distinct output structures, each handled by a specialized emitter class.
Tailwind v3 (JSON Theme Extension)
The TailwindEmitterHandler in packages/cli/src/linter/tailwind/handler.ts converts tokens into a JSON object matching Tailwind’s theme.extend schema. It transforms color maps into record<string, string> hex values, typography into nested arrays of [size, meta], and dimensions into plain strings like "4rem".
npx @google/design.md export --format json-tailwind DESIGN.md > tailwind.theme.json
Internally, the handler executes:
const handler = new TailwindEmitterHandler();
const result = handler.execute(state);
// result.data = { theme: { extend: { colors: {...}, spacing: {...} } } }
Tailwind v4 (CSS @theme Block)
For Tailwind v4, the TailwindV4EmitterHandler in packages/cli/src/linter/tailwind/v4/handler.ts generates CSS custom properties wrapped in an @theme block. It builds a TailwindV4ThemeData object and serializes it via serializeTailwindV4 from packages/cli/src/linter/tailwind/v4/serialize.ts, producing names like --color-primary and --spacing-lg.
npx @google/design.md export --format css-tailwind DESIGN.md > theme.css
The serialization process converts the intermediate data structure into valid CSS:
const handler = new TailwindV4EmitterHandler();
const result = handler.execute(state);
process.stdout.write(serializeTailwindV4(result.data.theme));
CSS Custom Properties (Variable Declarations)
The CssVarsEmitterHandler in packages/cli/src/linter/css-vars/handler.ts flattens token names (converting dots to hyphens) and emits an array of { name, value } declarations. The serializeCssVars helper then converts these into CSS custom property syntax. Users can prepend a custom prefix using the --prefix flag.
npx @google/design.md export --format css-vars --prefix my-app DESIGN.md > vars.css
This produces declarations like:
--my-app-color-primary: #ff0000;
W3C DTCG (Standard Token JSON)
The DtcgEmitterHandler in packages/cli/src/linter/dtcg/handler.ts maps tokens into the W3C Design Tokens Format (DTCG) schema. It adds required metadata fields including $schema, $type, and $value, producing JSON that validates against https://www.designtokens.org/schemas/2025.10/format.json.
npx @google/design.md export --format dtcg DESIGN.md > tokens.json
Extending Support for Different Data Structures
Adding support for new export formats requires three steps:
- Implement a new emitter class conforming to the
*EmitterSpecinterface that returns{ success: boolean, data: … }. - Register the format string in the
FORMATSconstant and add a dispatch branch inpackages/cli/src/commands/export.ts. - If the output isn't plain JSON, implement a serializer function (like
serializeTailwindV4orserializeCssVars) to handle the final string conversion.
This architecture ensures that the core linting and modeling logic remains untouched when introducing new export targets.
Summary
- The Design.md CLI handles different data structures for export through a strategy pattern using format-specific emitter classes.
- All emitters operate on a pure, side-effect-free
DesignSystemStatemodel defined inpackages/cli/src/linter/model/spec.ts. - Four built-in formats are supported: Tailwind v3 JSON, Tailwind v4 CSS, CSS custom properties, and W3C DTCG JSON.
- Each format has a dedicated handler:
TailwindEmitterHandler,TailwindV4EmitterHandler,CssVarsEmitterHandler, andDtcgEmitterHandler. - The system is extensible by implementing new emitters and registering them in the
FORMATSenum and export command dispatcher.
Frequently Asked Questions
What data structure does the Design.md CLI use as the source of truth for exports?
The CLI uses the DesignSystemState model defined in packages/cli/src/linter/model/spec.ts. This format-agnostic structure stores all tokens as Map objects (e.g., state.colors, state.typography), allowing emitters to transform the same underlying data into multiple output formats without duplication.
How do I export Design.md tokens to Tailwind CSS v4 format?
Use the css-tailwind format flag: npx @google/design.md export --format css-tailwind DESIGN.md. The TailwindV4EmitterHandler processes the state into a TailwindV4ThemeData object, which serializeTailwindV4 converts into a CSS @theme block containing custom properties like --color-primary.
Can I add a custom prefix to CSS variable exports?
Yes. When using the css-vars format, include the --prefix flag: npx @google/design.md export --format css-vars --prefix my-app DESIGN.md. The CssVarsEmitterHandler prepends this prefix to every token name, generating variables like --my-app-color-primary instead of --color-primary.
What is the DTCG export format and where is it implemented?
The DTCG (Design Tokens Community Group) format exports tokens as JSON conforming to the W3C Design Tokens Format specification. It is implemented in packages/cli/src/linter/dtcg/handler.ts by the DtcgEmitterHandler class, which structures output with $schema, $type, and $value fields required by the standard.
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 →