Skip to content

DTA with coding agents

A coding agent (Claude Code, Codex, Cursor’s agent) styles UI and edits token files like a person does, and it makes the same mistakes faster: a hex value where a token exists, a token name it made up, a token change nobody linted. DTA gives it four things, all plain CLI commands, so they work the same for people and CI:

Command What the agent gets
dta skill add Instructions: the workflow, the rules, what the exit codes mean
dta context This project’s brands, collections and the tokens to use (DESIGN.md)
dta token … Answers: the token for a value, what a token is in each brand
dta audit A check of its own code: hard-coded values that should be tokens

Everything here is read-only on the token files and Figma. Agents propose; dta push stays a person’s job, and the skill tells the agent so.

The output below is from the brand-ab test fixture.

$ dta skill add
Wrote .claude/skills/dta/SKILL.md.

The skill (SKILL.md with name and description front matter, the Agent Skills format) is the same for every project: look a token up before writing a value, the change loop (dta lint → dta build → dta diff), setting up visual review when asked (dta init --diff --yes, then dta diff --check), never dta push --yes or dta prune, never edit generated files, and every command with its purpose. Its command list comes from the CLI itself, so it cannot name a command that does not exist. For agents that read skills from another folder, use --dir. In CI, dta skill add --check exits with 1 when the installed skill is out of date, for example after a DTA update.

$ dta context
Wrote DESIGN.md and AGENTS.md.

DESIGN.md describes this project: how to switch brands, which collections exist and which of them code may use, the tokens to use with their CSS custom property, Tailwind name and value per brand, and the rules from your lint config. Primitives are never listed. A large token set is summarised by group, so the file stays around 2,000 tokens of text.

DESIGN.md (excerpt)
## Tokens to use
| Token | CSS | Tailwind | Value |
| --- | --- | --- | --- |
| Colors.Brand.Accent | `var(--colors-brand-accent)` | `--color-brand-accent` | #3b8fd9 |
| Colors.Brand.Primary | `var(--colors-brand-primary)` | `--color-brand-primary` | BrandA #0057b8, BrandB #3d5a80 |
…

AGENTS.md gets one marked block that points to it, for agents without skill support. DTA only ever changes that block; the rest of the file is yours:

AGENTS.md
<!-- dta:begin -->
<!-- Generated by `dta context`. DTA keeps this block up to date; the rest of the file is yours. -->
## Design tokens
Before you write a colour, size or font in code, read [DESIGN.md](DESIGN.md): the tokens to use and the rules. Look up a value with `dta token find '<value>'`. The DTA skill (`dta skill add`) has the workflow for changing tokens.
<!-- dta:end -->

Both files are generated: dta context --check exits with 1 when they are out of date. To keep them current without thinking about it, add a context section to the config (context: {}, Configuration) and dta build writes them too.

Instead of writing #003f8a, the agent asks:

$ dta token find '#003f8a'
2 tokens resolve to #003f8a:
Colors.Brand.Primary Dark BrandA var(--colors-brand-primary-dark)
Colors.Foundation.Blue.700 every mode var(--colors-foundation-blue-700)
Tokens the build writes come first, aliases (semantic tokens) before primitives: use the first that fits.

And before it uses a token:

$ dta token get Colors.Brand.Primary
Colors.Brand.Primary color · Brand(Alias)
BrandA #0057b8 Colors.Brand.Primary → Colors.Foundation.Blue.500
BrandB #3d5a80 Colors.Brand.Primary → Colors.Foundation.Navy.500
CSS var(--colors-brand-primary)
Tailwind --color-brand-primary

--format json gives both answers as JSON. All options: Token queries.

$ dta audit
src/Button.css
3:15 #0057b8 background → Colors.Brand.Primary var(--colors-brand-primary)
5:12 16px padding → Dimensions.spacing.4 var(--dimensions-spacing-4)
src/Card.tsx
3:30 #f3f4f6 bg-[…] → Colors.Surface.Muted var(--colors-surface-muted)
3:42 16px p-[…] → Dimensions.spacing.4 var(--dimensions-spacing-4)
3:71 Inter fontFamily → Typography.Body.Family var(--typography-body-family)
5 hard-coded values in 2 files could be tokens.

dta audit exits with 1 when it finds something, so the agent can loop until it is clean; a deliberate value gets a dta-audit-ignore comment. In an existing codebase, record today’s findings first (dta audit --update-baseline): later runs, and the agent, see only new ones. What it scans and how to add it to CI: CI with GitHub Actions.

What the skill asks of the agent when it changes token files:

  1. Edit the token files, never the build output.
  2. dta lint: exit code 2 means errors to fix.
  3. dta build.
  4. dta diff --tokens-only --format markdown: what changed and what depends on it. With stories configured, dta diff also shows the screenshots (dta diff in pull requests).
  5. Read the result, fix, repeat. Then a person reviews the pull request and pushes to Figma.

Design generators without a terminal (Stitch, v0, a chat) cannot run dta token find, so they invent colours. dta brief writes a short brief per brand to paste at the top of the prompt: the colours with a role (primary, accent, success …) and what each is for, text and surface colours, a neutral ramp, fonts, a few sizes, spacing, radii, breakpoints and rules. Colours meant for content on another colour (Text.On Primary) are left out of the text colours, neutrals are a ramp from dark to light, and fonts name the token that says what they are for. Values are those of the default breakpoint. Every list is capped, so a brief stays near 400 tokens. On brand-ab:

$ dta brief --brand BrandA
# BrandA brief
## Colors
- Primary: `#0057b8` (Colors.Brand.Primary): main actions, links, brand moments
- Accent: `#3b8fd9` (Colors.Brand.Accent): emphasis, used sparingly
- Text: `#111827` (Colors.Text.Default), `#6b7280` (Colors.Text.Muted)
- Surfaces: `#ffffff` (Colors.Surface.Default), `#f3f4f6` (Colors.Surface.Muted)
- Neutrals, dark to light: `#111827` `#6b7280` `#f3f4f6` `#ffffff`
## Typography
- Fonts: Inter (Typography.Body.Family), Source Serif 4 (Typography.FontFamily.Serif)
- Sizes: 14, 16, 20, 28 (px)
## Spacing
4, 8, 16, 32 (px)
## Radius
4, 8, 999 (px)
## Breakpoints
Mobile (default), Tablet (min-width: 768px), Desktop (min-width: 1024px)
## Rules
- Use only the values in this brief: no other colours, sizes or fonts.

brief.identity in the config adds a line on the brand’s character and brief.rules replaces the generic rule, since neither can be read from tokens:

dta.config.ts
brief: {
identity: 'Calm, trustworthy, institutional.',
rules: ['Never use pure black: use Colors.Text.Default.', 'Radius is never 0 on buttons.'],
},

Without --brand there is one brief per brand. --format json gives the same content as data ("schema": "dta.brief/1") for a tool’s API, and --copy puts the brief on the clipboard (pbcopy, clip, wl-copy, xclip or xsel).

All of this assumes an agent that can run commands. For chat or design tools without a terminal, an MCP server over the same queries is planned but not built. Until then, a brief, /llms.txt and the JSON Schemas are what such a tool can read.