AI Development Guide for Espalier Consumers

AI coding tools produce better results when they understand the design system they are working with. This guide explains the Espalier token contract, lists the mistakes AI tools make most often, and provides copy-pasteable instructions you can drop into your project's CLAUDE.md, AGENTS.md, or .github/copilot-instructions.md.

It also shows how to set up a lightweight automated check that catches invalid token references before they reach production.


The Espalier Token Contract

Espalier exposes CSS custom properties at three levels. Understanding which level to target is the single most important thing an AI tool needs to get right.

1. Root-theme tokens

Computed by <esp-root> and inherited by every descendant. These are the design system's foundational values.

Prefix Examples
--esp-color-* --esp-color-text, --esp-color-border, --esp-color-action-background
--esp-type-* --esp-type-normal, --esp-type-large
--esp-size-* --esp-size-padding, --esp-size-small-to-normal
--esp-font-* --esp-font-body, --esp-font-weight-headings
--esp-l-* --esp-l-surface, --esp-l-text (lightness ramp)

These tokens should be consumed, never overridden in application CSS. To change them system-wide, update the theme object passed to <esp-root>.

2. Shared hooks

Cross-component tokens used by field-like controls and overlays.

Token Purpose
--esp-field-background Background for inputs, selects, textareas
--esp-field-border-color Border for field controls
--esp-field-text-color Text inside field controls
--esp-vellum-background Overlay backdrop color

These can be overridden on a per-section basis when you need a group of fields to share a non-default appearance.

3. Component-level tokens

Scoped to individual components for targeted overrides.

/* Override tooltip width for a specific context */
.my-panel {
  --esp-tooltip-max-width: 60ch;
}

Every component's tokens are listed in the Styling Reference and in the published espalier.token-manifest.json.

Private tokens

Tokens prefixed with --_esp- are private implementation details. They are not part of the public contract, may change without notice, and must never be referenced or overridden.

Variants

When a component subtree needs a different color family, set the variant attribute instead of overriding individual color tokens. This keeps semantic roles intact:

<!-- Correct: variant shifts the whole color context -->
<esp-button label="Save" variant="success"></esp-button>

<!-- Wrong: manually overriding breaks semantic coherence -->
<esp-button label="Save" style="--esp-button-background: green;"></esp-button>

For more detail on when to use each lever, see Tokens & Variants.


Common AI Anti-Patterns

Hardcoded colors instead of tokens

/* Wrong */
.card { color: #333; background: white; }

/* Correct */
.card { color: var(--esp-color-text); background: var(--esp-color-layer-1); }

Ad-hoc oklch() mixed with the token system

/* Wrong — breaks when the theme changes */
.badge { background: oklch(0.7 0.15 150); }

/* Correct — derive from the design system */
.badge { background: var(--esp-color-action-background); }

Overriding semantic tokens that should stay internal

/* Wrong — bypasses APCA contrast enforcement */
:root { --esp-color-text: navy; }

/* Correct — change the theme configuration instead */

Theme-level tokens are computed from a seed color through perceptual color math and contrast enforcement. Overriding them directly breaks the pipeline.

Fabricating token names

/* Wrong — this token does not exist */
.sidebar { padding: var(--esp-size-sidebar-padding); }

/* Correct — use a real token from the contract */
.sidebar { padding: var(--esp-size-padding); }

AI tools sometimes invent plausible-sounding token names. Every reference should be validated against the published manifest or Styling Reference.

Ignoring variant context

/* Wrong — hardcodes a specific variant color */
.alert { border-color: var(--esp-color-danger); }

/* Correct — use variant for semantic shifts */
<esp-info variant="danger">Something went wrong.</esp-info>

Prompt Fragments

Copy these into your project's CLAUDE.md, AGENTS.md, or .github/copilot-instructions.md. Adjust paths and project names as needed.

Core instructions

## Espalier Design System

This project uses the Espalier design system (`@taprootio/taproot-controls`).

### Token rules

- Use `--esp-*` CSS custom properties for all colors, spacing, typography,
  and layout values. Never hardcode hex, rgb, oklch, or pixel values when
  a token exists.
- Never override root-theme tokens (`--esp-color-*`, `--esp-type-*`,
  `--esp-size-*`, `--esp-font-*`, `--esp-l-*`) in application CSS.
  To change them, update the theme object on `<esp-root>`.
- Never reference or override private tokens (`--_esp-*`).
- Use component-level tokens (`--esp-{component}-*`) for targeted
  overrides. Check the Espalier Styling Reference to confirm the token
  exists before using it.
- Use the `variant` attribute for semantic color shifts. Do not manually
  override color tokens to simulate a variant.

### Verification

- Run the consumer token check (see below) before considering styling
  work complete.
- Reference `node_modules/@taprootio/taproot-controls/espalier.token-manifest.json`
  as the source of truth for valid token names.

Extended instructions (with layering model)

### Espalier layering model

Espalier uses a three-tier token hierarchy:

1. **Theme fields** — configured on `<esp-root>` (seed color, font
   families, scale ratios, semantic mappings). Change these for
   system-wide shifts.
2. **Semantic tokens** — emitted as CSS custom properties by
   `<esp-root>` (`--esp-color-text`, `--esp-size-padding`, etc.).
   Consume these in application CSS but never override them.
3. **Component hooks** — scoped tokens for individual component
   overrides (`--esp-tooltip-max-width`, `--esp-grid-header-background`).
   Override these when a specific component instance needs a local change.

When deciding which level to target:
- "The whole product should look different" → theme fields
- "This one button should be wider" → component hook
- "This section should use a different color family" → `variant` attribute

Consumer-Side Contract Testing

The published espalier.token-manifest.json lets you verify that your CSS only references valid Espalier tokens. Here is a minimal test you can drop into any project that uses Vitest (adapt for Jest or another runner as needed).

Install

The manifest ships inside the @taprootio/taproot-controls package. No extra install is required.

Test file

// __tests__/espalier-token-contract.test.js
import { readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { describe, it, expect } from "vitest";
import manifest from "@taprootio/taproot-controls/espalier.token-manifest.json" with { type: "json" };

const VALID_TOKENS = new Set(manifest.tokens.map((t) => t.name));
const TOKEN_RE = /var\(\s*(--esp-[a-z0-9-]+)/g;

/** Recursively collect files matching an extension. */
function walk(dir, ext, files = []) {
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
    const full = join(dir, entry.name);
    if (entry.isDirectory() && !entry.name.startsWith("node_modules")) {
      walk(full, ext, files);
    } else if (entry.isFile() && full.endsWith(ext)) {
      files.push(full);
    }
  }
  return files;
}

describe("espalier token contract", () => {
  it("only references valid Espalier tokens", () => {
    // Adjust these paths to match your project structure.
    const files = [
      ...walk("src", ".ts"),
      ...walk("src", ".css"),
    ];

    const invalid = [];

    for (const file of files) {
      const text = readFileSync(file, "utf8");
      TOKEN_RE.lastIndex = 0;
      let match;
      while ((match = TOKEN_RE.exec(text))) {
        if (!VALID_TOKENS.has(match[1])) {
          invalid.push({ token: match[1], file });
        }
      }
    }

    if (invalid.length > 0) {
      const summary = invalid
        .map((i) => `  ${i.token} in ${i.file}`)
        .join("\n");
      expect.fail(
        `Found references to unknown Espalier tokens:\n${summary}`,
      );
    }
  });
});

This test scans your source for var(--esp-*) references and compares each one against the manifest. Unknown tokens fail the test with a clear message showing the token name and file.

CI integration

Add the test to your existing test suite or run it as a dedicated check:

npx vitest run __tests__/espalier-token-contract.test.js

How Espalier Protects Its Own Contract

Espalier runs three internal checks in CI:

  1. Design-token contract test — verifies that every var(--esp-*) reference in source has a matching declaration, @cssprop annotation, runtime setter, or root-theme emitter.
  2. Styling-guide coverage test — ensures docs/guides/styling.md documents every public token and lists no unknown ones.
  3. Token manifest sync test — verifies that espalier.token-manifest.json matches the source-derived token set. A stale manifest fails CI.

These run via npm run verify-design-tokens and block merges when tokens drift out of sync. The consumer test described above is the downstream equivalent — it verifies your application against the same contract that Espalier enforces internally.


Further Reading

Where does this show?