# How to Handle Circular Token References in DESIGN.md: Detection and Resolution

> Fix brokenref errors in DESIGN.md. Learn to detect and resolve circular token references by ensuring reference chains terminate at primitive values.

- Repository: [Google Labs Code/design.md](https://github.com/google-labs-code/design.md)
- Tags: how-to-guide
- Published: 2026-06-28

---

**Circular token references in DESIGN.md are treated as invalid by the linter, which reports them as `broken-ref` errors; you must break the cycle by ensuring every reference chain terminates at a primitive value.**

Circular token references occur when a design token points back to itself through a chain of dependencies, preventing the system from computing concrete values. In the [`google-labs-code/design.md`](https://github.com/google-labs-code/design.md/blob/main/google-labs-code/design.md) repository, the linter treats these references as errors and halts downstream exports until resolved. Understanding how to detect and eliminate these cycles is essential for maintaining valid design token systems.

## What Are Circular Token References?

A **circular token reference** happens when a token resolves (directly or indirectly) to itself, creating an infinite resolution loop. According to the DESIGN.md specification, these references are **invalid** because they prevent the computation of concrete values needed for downstream exports like Tailwind CSS or JSON Design Token Group (DTG) formats.

When the resolver encounters a loop, it cannot determine a final primitive value (such as a hex color or pixel dimension), causing the export pipeline to stall. The linter explicitly forbids this pattern to ensure token integrity across all output targets.

## How the Linter Detects Circular References

### The Token Reference Syntax

References in DESIGN.md use the syntax `{path.to.token}` inside YAML frontmatter. As defined in [`docs/spec.md`](https://github.com/google-labs-code/design.md/blob/main/docs/spec.md), every reference must resolve to a **primitive value**—a literal string, number, or color—not to another reference.

### The Detection Algorithm

The linter’s detection logic resides in two key locations:

- **[`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 `resolveReference` function executes the resolution logic. When it encounters a circular chain, it returns `null` to prevent infinite recursion and signals that the reference is unresolvable.

- **[`packages/cli/src/linter/linter/rules/broken-ref.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/rules/broken-ref.ts)**: This rule implements the validation logic. It walks the token tree and maintains a stack of visited tokens. If a reference chain revisits a token already on the stack, the resolver records a `circular-ref` error (displayed as "Broken/circular references").

The test suite in [`packages/cli/src/linter/linter/rules/broken-ref.test.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/rules/broken-ref.test.ts) demonstrates specific detection scenarios for circular reference chains.

## Resolving Circular References

### Step 1: Identify the Cycle

Run the linter to surface the exact location of the circular dependency:

```bash
npx @google/design.md lint path/to/DESIGN.md

```

The CLI output pinpoints the problematic chain:

```

error: circular reference detected at components.button-primary.backgroundColor -> colors.primary-60 -> components.button-primary.backgroundColor

```

### Step 2: Break the Cycle

Restructure your tokens so that every reference points to a concrete primitive. Two effective strategies include:

- **Extract primitives**: Move concrete values out of component sections into dedicated token groups like `colors` or `spacing`.
- **Duplicate values**: If the same primitive is needed in multiple places, define it literally in the base token and reference it from components, never referencing back to a component token.

### Step 3: Validate the Fix

Re-run the linter until no `circular reference` errors remain:

```bash
npx @google/design.md lint examples/totality-festival/DESIGN.md

```

A successful validation prints:

```

✔︎  design.md passes all linter checks

```

## Code Examples

### Problematic Definition

The following YAML creates a circular dependency between a component token and a color token:

```yaml
---
components:
  badge:
    backgroundColor: "{colors.primary}"
colors:
  primary: "{components.badge.backgroundColor}"
---

```

Here, `components.badge.backgroundColor` references `colors.primary`, which immediately references back to `components.badge.backgroundColor`. The resolver detects this loop and reports a `circular-ref` error.

### Corrected Definition

Break the cycle by assigning a primitive value to the base token:

```yaml
---
components:
  badge:
    backgroundColor: "{colors.primary}"
colors:
  primary: "#2A9D8F"
---

```

Now `colors.primary` holds a literal color value, allowing the reference chain to terminate successfully.

### Shared Primitive Pattern

Multiple components can safely reference the same primitive without creating cycles:

```yaml
---
colors:
  primary: "#2A9D8F"
components:
  badge:
    backgroundColor: "{colors.primary}"
  button:
    backgroundColor: "{colors.primary}"
---

```

Both components consume the primitive value from `colors.primary`, maintaining a one-way dependency graph.

## CLI Usage

Use the following command to lint your DESIGN.md files and detect circular references:

```bash

# Lint a specific file

npx @google/design.md lint examples/totality-festival/DESIGN.md

# Expected output for valid files

✔︎  design.md passes all linter checks

```

If a circular reference exists, the CLI outputs a concise error indicating the token path:

```

✖  broken-ref: circular reference detected at components.button-primary.backgroundColor -> colors.primary-60 -> components.button-primary.backgroundColor

```

## Summary

- **Circular references are invalid** according to the DESIGN.md specification and block downstream exports.
- **Detection occurs** in [`packages/cli/src/linter/linter/rules/broken-ref.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/rules/broken-ref.ts) using a stack-based traversal that identifies revisiting tokens.
- **Resolution fails safely** when `resolveReference` 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) returns `null` for circular chains.
- **Fix cycles** by ensuring all reference chains terminate at primitive values in dedicated token groups.
- **Validation** requires running `npx @google/design.md lint` until no `circular-ref` errors remain.

## Frequently Asked Questions

### What error code does the linter use for circular references?

The linter emits a `broken-ref` error with the specific classification `circular-ref`, displayed in the CLI as "Broken/circular references." This error appears alongside the file location and the exact token path that forms the cycle.

### How does the resolver prevent infinite loops during token resolution?

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) tracks the resolution stack. When it detects that a reference chain has returned to a token already being processed, it immediately returns `null` and halts further resolution, preventing infinite recursion.

### Can I reference a token that references another token?

Yes, **chained references are supported** as long as they eventually terminate at a primitive value. The DESIGN.md specification only prohibits cycles where a reference chain loops back to itself. You can safely reference `colors.primary` from `components.button`, even if `colors.primary` itself references `colors.base`, provided `colors.base` contains a concrete value.

### Where is the broken-ref rule implemented?

The rule implementation resides in [`packages/cli/src/linter/linter/rules/broken-ref.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/rules/broken-ref.ts). This file contains the logic for detecting both broken references (non-existent tokens) and circular references (tokens that reference themselves). The corresponding test suite in [`packages/cli/src/linter/linter/rules/broken-ref.test.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/rules/broken-ref.test.ts) provides examples of valid and invalid reference patterns.