How the Diff Command Detects Token-Level Changes Between Design Systems

The diff command detects token-level changes between design systems by linting two DESIGN.md files, extracting their design system token maps, and comparing them key-by-key using a diffMaps utility that classifies each token as added, removed, or modified based on JSON string equality.

The diff command in the google-labs-code/design.md repository provides precise detection of token-level changes between design system specifications. By parsing DESIGN.md files into structured token maps and applying a custom comparison algorithm, the tool identifies exactly which colors, typography scales, spacing values, and component definitions have changed between versions.

Linting and Extracting Design System State

The comparison process begins in packages/cli/src/commands/diff.ts, where the command reads two DESIGN.md files and processes them through the linter. The readInput utility ingests file content, then the lint function parses each file into a DesignSystemState object containing token maps for colors, typography, rounding, spacing, and components.

const beforeReport = lint(beforeContent);
const afterReport  = lint(afterContent);

Source: packages/cli/src/commands/diff.ts lines 46-48

These DesignSystemState objects provide the structured data required for comparison, with each token category stored as a Map<string, V> where keys represent token names and values contain the token definitions.

Serializing Component Definitions

Component tokens require special handling before comparison. In the design.md specification, components are stored as Map<string, ComponentDef> objects. The serializeComponents function (defined in packages/cli/src/commands/diff.ts lines 77-83) converts these complex structures into plain objects by extracting the properties entries, making structural differences comparable via standard equality checks.

function serializeComponents(components: Map<string, ComponentDef>) {
  const result = new Map<string, Record<string, unknown>>();
  for (const [name, comp] of components) {
    result.set(name, Object.fromEntries(comp.properties));
  }
  return result;
}

This serialization ensures that component property changes surface as standard object modifications during the diff process.

The diffMaps Algorithm

The core comparison logic resides in packages/cli/src/utils.ts within the diffMaps<V> function. This utility receives two Map<string, V> objects and returns three arrays categorizing changes: added, removed, and modified.

The algorithm detects modifications by comparing JSON.stringify representations of values, ensuring deep equality checks for complex token definitions:

export function diffMaps<V>(before: Map<string, V>, after: Map<string, V>) {
  const added: string[] = [];
  const removed: string[] = [];
  const modified: string[] = [];

  for (const key of after.keys()) {
    if (!before.has(key)) added.push(key);
    else if (JSON.stringify(before.get(key)) !== JSON.stringify(after.get(key)))
      modified.push(key);
  }

  for (const key of before.keys()) {
    if (!after.has(key)) removed.push(key);
  }
  return { added, removed, modified };
}

Source: packages/cli/src/utils.ts lines 61-84

This approach guarantees that any structural change to a token value—whether a color hex code adjustment or a typography scale modification—triggers a modification classification.

Aggregating Token Diffs

After processing, the command aggregates diffs across all five token categories. For each category (colors, typography, rounded, spacing, and components), the command invokes diffMaps with the respective maps from the DesignSystemState objects:

const diff = {
  tokens: {
    colors:    diffMaps(beforeReport.designSystem.colors,    afterReport.designSystem.colors),
    typography:diffMaps(beforeReport.designSystem.typography,afterReport.designSystem.typography),
    rounded:   diffMaps(beforeReport.designSystem.rounded,   afterReport.designSystem.rounded),
    spacing:   diffMaps(beforeReport.designSystem.spacing,   afterReport.designSystem.spacing),
    components:diffMaps(
      serializeComponents(beforeReport.designSystem.components),
      serializeComponents(afterReport.designSystem.components)
    ),
  },
  // ...
};

Source: packages/cli/src/commands/diff.ts lines 50-59

The resulting structured diff object contains granular change arrays for each token type, enabling precise reporting of exactly which design tokens were added, removed, or modified.

CLI Usage and Output Format

Invoke the diff command via the CLI to compare two DESIGN.md files:


# Compare two DESIGN.md files

design diff path/to/old/DESIGN.md path/to/new/DESIGN.md --format json

The command outputs a structured JSON object containing the classified changes and regression status:

{
  "tokens": {
    "colors": {
      "added": ["brand-primary"],
      "removed": [],
      "modified": ["neutral-100"]
    },
    "typography": {
      "added": [],
      "removed": [],
      "modified": ["heading-1"]
    },
    "components": {
      "added": ["Button"],
      "removed": [],
      "modified": ["Card"]
    }
  },
  "findings": {
    "before": { "errors": 0, "warnings": 1 },
    "after":  { "errors": 0, "warnings": 2 },
    "delta":  { "errors": 0, "warnings": 1 }
  },
  "regression": true
}

The CLI exits with a non-zero status when regressions are detected—specifically when the "after" state introduces new errors or warnings compared to the "before" state.

Programmatic Integration

You can leverage the diff functionality programmatically in Node.js applications by importing the linter and utility functions directly:

import { readFile } from 'fs/promises';
import { lint } from '@design/cli/linter';
import { diffMaps } from '@design/cli/utils';

async function diffDesignSystems(oldPath: string, newPath: string) {
  const oldContent = await readFile(oldPath, 'utf8');
  const newContent = await readFile(newPath, 'utf8');

  const oldReport = lint(oldContent);
  const newReport = lint(newContent);

  const colorDiff = diffMaps(oldReport.designSystem.colors, newReport.designSystem.colors);
  console.log('Color changes:', colorDiff);
}

This approach enables custom workflows for design system validation in CI/CD pipelines or automated testing suites.

Summary

  • The diff command uses the diffMaps utility in packages/cli/src/utils.ts to compare token maps key-by-key
  • Changes are classified as added, removed, or modified using JSON stringification for equality checks
  • Component definitions require serialization via serializeComponents before comparison to flatten complex property structures
  • The CLI exits with non-zero status when regressions (new errors or warnings) are introduced between versions
  • Token-level changes are reported across five categories: colors, typography, rounded, spacing, and components

Frequently Asked Questions

What file format does the diff command expect?

The diff command expects DESIGN.md files that conform to the design.md specification. These files are parsed into DesignSystemState objects containing typed token maps for colors, typography, spacing, and component definitions.

How does diffMaps detect modified tokens?

The diffMaps function detects modifications by comparing JSON.stringify representations of values for keys present in both maps. If the stringified values differ, the key is classified as modified, ensuring deep comparison of complex token objects.

Can I use the diff functionality programmatically without the CLI?

Yes. You can import lint from the linter module and diffMaps from the utils module to perform token comparisons programmatically. This enables integration into custom build tools, testing frameworks, or automation scripts.

What constitutes a regression in the diff output?

A regression occurs when the "after" state contains more errors or warnings than the "before" state. The diff output includes a regression boolean flag and the CLI exits with a non-zero status code when regressions are detected, making it suitable for CI/CD quality gates.

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 →