# DESIGN.md Token Reference: How Cross-Token Resolution Works in google-labs-code/design.md

> Learn how DESIGN.md tokens reference other tokens using curly braces. Discover the three-phase symbol table process for nested chains, circular dependency detection, and depth limits.

- Repository: [Google Labs Code/design.md](https://github.com/google-labs-code/design.md)
- Tags: internals
- Published: 2026-07-03

---

**Yes, DESIGN.md tokens can reference other tokens using curly-brace syntax like `{colors.primary}`, and the resolution works through a three-phase symbol table process that handles nested chains, detects circular dependencies, and enforces configurable depth limits.**

The [`google-labs-code/design.md`](https://github.com/google-labs-code/design.md/blob/main/google-labs-code/design.md) specification allows **DESIGN.md token references** to create dependencies between values, enabling single-source-of-truth architectures where base tokens drive derived values. Understanding how the CLI resolves these references is essential for debugging complex token hierarchies and avoiding circular dependency errors.

## Curly-Brace Syntax for Token References

In a [`DESIGN.md`](https://github.com/google-labs-code/design.md/blob/main/DESIGN.md) file, tokens reference other tokens using **curly-brace reference syntax** defined in [`docs/spec.md`](https://github.com/google-labs-code/design.md/blob/main/docs/spec.md):

```yaml
colors:
  primary: "#1A1C1E"
  primary-60: "{colors.primary}"
  text-inverse: "{colors.primary-60}"

components:
  button-primary:
    backgroundColor: "{colors.primary-60}"
    rounded: "{rounded.md}"

```

The reference path inside the braces (e.g., `colors.primary-60`) points to any other token defined in the YAML front-matter. References can appear anywhere a literal value is allowed, including colors, dimensions, typography definitions, and component properties.

## The Three-Phase Resolution Process

According to the source code in [`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts), the CLI resolves token references through a **symbol table** that maps fully-qualified token names to their values. The resolution occurs in three distinct phases.

### Phase 1: Building the Symbol Table

The handler first parses all tokens and populates the symbol table. Primitive values (hex colors, `px`/`rem` dimensions, typography objects) are stored directly, while reference strings are stored raw for later resolution.

Relevant code from lines 57-64 and 86-94:

```ts
if (typeof raw === 'string' && isTokenReference(raw)) {
    // Store raw reference for later resolution
    symbolTable.set(`colors.${name}`, raw);
} else if (isValidColor(raw)) {
    const resolved = parseColor(raw);
    colors.set(name, resolved);
    symbolTable.set(`colors.${name}`, resolved);
}

```

This phase ensures every token path exists in the table, whether as a concrete value or an unresolved reference string.

### Phase 2: Chained Reference Resolution

After initial parsing, the system walks the token trees again to resolve references. The `resolveReference` function (lines 77-96) handles this recursively:

```ts
function resolveReference(symbolTable, path, visited, depth = 0) {
    if (depth > MAX_REFERENCE_DEPTH) return null;
    if (visited.has(path)) return null;          // circular reference
    visited.add(path);
    const value = symbolTable.get(path);
    if (value === undefined) return null;
    if (typeof value === 'string' && isTokenReference(value)) {
        const innerPath = value.slice(1, -1);
        return resolveReference(symbolTable, innerPath, visited, depth + 1);
    }
    return value;                               // concrete token (color, dimension…)
}

```

This algorithm supports **nested references** (a token that itself references another token) by recursively following chains until it reaches a concrete value or hits a safety limit.

### Phase 3: Component Construction

During component evaluation, property values undergo the same resolution process. Lines 85-100 illustrate this:

```ts
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);
        properties.set(propName, rawValue);
    }
}

```

If resolution fails, the raw reference string is preserved and recorded as an unresolved reference for the linter to report.

## Safety Guarantees and Validation

The resolution process provides several safeguards against malformed references and infinite loops:

- **Cycle Detection**: The `visited` Set in `resolveReference` prevents infinite loops; circular references return `null` and trigger linter errors.
- **Depth Limits**: `MAX_REFERENCE_DEPTH` and `MAX_TOKEN_NESTING_DEPTH` (defined in [`packages/cli/src/linter/spec-config.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-config.ts)) protect against excessively deep chains.
- **Type Validation**: After resolution, the system verifies that resolved values match expected types (e.g., `resolved.type === 'color'` for color tokens).
- **Error Reporting**: Unresolved or invalid references are added to the `findings` array and surfaced by the CLI.

## Practical Examples

### Simple Token References

```yaml
colors:
  primary: "#1A1C1E"
  primary-60: "{colors.primary}"

```

After Phase 2 resolution, `colors.primary-60` resolves to the same hex value `#1A1C1E` as `colors.primary`.

### Chained Reference Resolution

```yaml
colors:
  base: "#112233"
  shade: "{colors.base}"
  tint: "{colors.shade}"

```

The resolver follows the chain `tint → shade → base` and returns the concrete hex `#112233`.

### Component Usage

```yaml
components:
  button-primary:
    backgroundColor: "{colors.primary-60}"
    rounded: "{rounded.md}"
    padding: "12px"

```

During Phase 3, the handler looks up `colors.primary-60` (already resolved to a color object) and [`rounded.md`](https://github.com/google-labs-code/design.md/blob/main/rounded.md) (a dimension object). The final component definition contains the actual resolved values, ready for downstream exporters like Tailwind or DTCG.

### Circular Reference Detection

```yaml
colors:
  a: "{colors.b}"
  b: "{colors.a}"

```

`resolveReference` detects the cycle when `visited.has(path)` returns true, returns `null`, and the linter records an error in the `findings` array.

## Summary

- **DESIGN.md token references** use curly-brace syntax (`{path.to.token}`) to point to other tokens.
- Resolution occurs in three phases: **symbol table construction**, **chained reference resolution**, and **component construction**.
- The `resolveReference` function in [`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts) handles recursive lookups with **cycle detection** via a `visited` Set.
- **Depth limits** (`MAX_REFERENCE_DEPTH`) prevent runaway resolution chains.
- Failed resolutions are preserved as raw strings and reported as linter errors.

## Frequently Asked Questions

### What is the exact syntax for referencing tokens in DESIGN.md?

Use curly braces containing the full token path: `{colors.primary}` or `{rounded.md}`. This syntax is enforced by the `isTokenReference` predicate in the linter's model specification.

### How does the system handle circular references between tokens?

The `resolveReference` function tracks visited paths using a Set. If it encounters a path already in the set, it returns `null` to break the cycle, and the linter reports the error.

### What is the maximum depth for token reference chains?

The depth is limited by `MAX_REFERENCE_DEPTH` and `MAX_TOKEN_NESTING_DEPTH`, configured in [`packages/cli/src/linter/spec-config.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-config.ts). Exceeding these limits causes the resolver to return `null`.

### Where is the reference resolution logic implemented?

The core implementation lives in [`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts), specifically in the `resolveReference` function (lines 77-96) and the three-phase model building process.