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:
- Design-token contract test — verifies that every
var(--esp-*)reference in source has a matching declaration,@csspropannotation, runtime setter, or root-theme emitter. - Styling-guide coverage test — ensures
docs/guides/styling.mddocuments every public token and lists no unknown ones. - Token manifest sync test — verifies that
espalier.token-manifest.jsonmatches 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
- Styling Reference — complete table of every CSS custom property
- Tokens & Variants — when to use theme fields, component props, or
variant - Semantic Color Groups — role-based grouping model for the 19 semantic colors
- Perceptual Color Engine — OKLCH, APCA, and how the palette is computed