How DESIGN.md Token Reference Resolution Works: The `{path.to.token}` Syntax Explained

DESIGN.md resolves token references using a three-phase algorithm that first parses primitive values into a symbol table, then recursively resolves chained references with cycle detection and depth limits, and finally substitutes concrete values into component definitions.

The google-labs-code/design.md CLI processes design tokens defined in YAML front-matter, where values can reference other tokens using the {path.to.token} syntax. This resolution mechanism allows design systems to create reusable, chained token values while preventing infinite loops and handling unresolved references.

The Three Phases of Token Resolution

The resolver operates sequentially through three distinct phases implemented in packages/cli/src/linter/model/handler.ts.

Phase 1: Initial Primitive Parsing

During the first pass, the ModelHandler.execute method (lines 55-74) parses all non-reference values and populates a symbol table. Primitive values such as colors, typography, and dimensions are stored as resolved objects, while raw reference strings remain unprocessed.

// From handler.ts lines 55-74
symbolTable.set(`colors.${name}`, resolved);    // primitive color stored as object
symbolTable.set(`rounded.${name}`, raw);       // raw reference string stored as-is

This symbol table serves as the lookup registry for all subsequent resolution steps.

Phase 2: Chained Reference Resolution

After primitive parsing, the system resolves raw reference strings using the resolveReference function (lines 73-96 of handler.ts). This recursive algorithm handles chained references—where one token points to another—until a concrete value is reached.

function resolveReference(symbolTable, path, visited, depth = 0) {
  if (depth > MAX_REFERENCE_DEPTH) return null;     // depth limit guard
  if (visited.has(path)) return null;               // circular reference guard
  visited.add(path);
  
  const value = symbolTable.get(path);
  if (typeof value === 'string' && isTokenReference(value)) {
    const innerPath = value.slice(1, -1);           // strip curly braces
    return resolveReference(symbolTable, innerPath, visited, depth + 1);
  }
  return value;
}

The resolver implements two critical safety mechanisms:

  • Depth limiting via MAX_REFERENCE_DEPTH to prevent infinite recursion
  • Cycle detection using a visited Set to catch circular token references

Phase 3: Component Construction

In the final phase (lines 84-101 of handler.ts), component definitions are processed. Each property containing a token reference is evaluated using resolveReference. If resolution fails, the reference is recorded for error reporting.

// From handler.ts lines 84-101
if (isTokenReference(rawValue)) {
  const refPath = rawValue.slice(1, -1);
  const resolved = resolveReference(symbolTable, refPath, new Set());
  if (resolved !== null) properties.set(propName, resolved);
  else unresolvedRefs.push(rawValue);    // track unresolvable references
}

Recursive Resolution Algorithm and Safety Guards

The resolveReference function in packages/cli/src/linter/model/handler.ts follows a strict recursive pattern to traverse token chains. When encountering a reference string like {colors.primary-60}, the algorithm strips the curly braces and queries the symbol table for the inner path. If the retrieved value is another reference string, the function calls itself with an incremented depth counter.

The circular guard uses a visited Set that tracks all paths encountered during the current resolution chain. If a path appears twice in the same chain, the function returns null immediately. Similarly, exceeding MAX_REFERENCE_DEPTH triggers an early termination, ensuring the CLI cannot enter infinite loops through malformed token definitions.

Practical Example: Resolving Component Tokens

Consider this DESIGN.md front-matter excerpt:

---
colors:
  primary-60: "#0D47A1"
  primary-20: "#1976D2"
typography:
  label-md:
    fontFamily: Public Sans
    fontSize: 14px
components:
  button-primary:
    backgroundColor: "{colors.primary-60}"
    textColor: "{colors.primary-20}"
    typography: "{typography.label-md}"
---

The resolution process follows this sequence:

  1. Parse primitives: colors.primary-60, colors.primary-20, and typography.label-md are parsed into concrete objects and stored in the symbol table.
  2. Resolve references: button-primary.backgroundColor triggers resolveReference(symbolTable, "colors.primary-60", new Set()), which returns the color object #0D47A1.
  3. Construct model: The component object is populated with fully resolved values, ready for export to Tailwind CSS or DTCG format.

Implementation Files and Specification Reference

The token reference behavior is formally defined in two key locations:

  • docs/spec.md (lines 86-87): Specifies that token references must be wrapped in curly braces and point to primitive values, except within the components section where composite values are permitted.
  • packages/cli/src/linter/model/handler.ts: Contains the complete resolution implementation, including ModelHandler.execute, the resolveReference recursive function, and the component construction logic.

According to the specification in docs/spec.md, the {path.to.token} syntax requires the inner path to resolve to a primitive design token value, ensuring type consistency across the design system.

Summary

  • Three-phase resolution: Primitive parsing populates the symbol table, chained references are resolved recursively, and components are constructed with concrete values.
  • Safety guards: The resolveReference function uses MAX_REFERENCE_DEPTH and a visited Set to prevent infinite loops and detect circular dependencies.
  • Unresolvable tracking: Failed references are collected in unresolvedRefs for diagnostic reporting during the CLI linting process.
  • Spec compliance: The official specification in docs/spec.md defines the syntax constraints and value requirements for token references.

Frequently Asked Questions

What is the maximum depth for token reference chains in DESIGN.md?

The resolution algorithm enforces a maximum depth limit through the MAX_REFERENCE_DEPTH constant defined in packages/cli/src/linter/model/handler.ts. While the exact numeric value is implementation-dependent, the guard prevents infinite recursion by terminating resolution attempts that exceed this threshold, returning null for excessively deep chains.

How does DESIGN.md detect circular token references?

Circular references are detected using a visited Set that tracks all paths encountered during the current resolution chain. In the resolveReference function (lines 73-96 of handler.ts), if a path already exists in the visited Set, the function immediately returns null, breaking the cycle and preventing infinite recursion.

Where is the official token reference syntax defined?

The official specification for the {path.to.token} syntax appears in docs/spec.md at lines 86-87. This document specifies that references must be wrapped in curly braces and typically resolve to primitive values, with the exception of component definitions where composite values are permitted.

What happens when a token reference cannot be resolved?

When resolveReference returns null for a reference—either due to a missing token, circular dependency, or depth limit violation—the unresolved reference string is pushed to the unresolvedRefs array (lines 84-101 of handler.ts). The CLI subsequently reports these unresolved references to help developers identify missing or invalid token definitions.

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 →