Design to code
Design tokens as CSS variables an AI coding agent can follow
Set up design tokens as CSS variables that an AI coding agent follows: two layers, clear names, themes, a repository rule and a check for raw values.
Design tokens as CSS variables means declaring each design decision (a color, a spacing step, a radius) once, as a custom property on :root such as --color-action, and using it everywhere with var(). An AI coding agent follows them reliably when there are few of them, they are named for their role, they live in one file, and a rule in the repository forbids raw values in components.
What a design token is, in CSS terms
A design token is a named design decision. In CSS, the simplest way to express one is a custom property:
:root {
--color-action: #0011ff;
--space-4: 16px;
--radius-md: 8px;
}
.button {
background: var(--color-action);
padding: var(--space-4);
border-radius: var(--radius-md);
}
A few behaviors of custom properties matter for tokens (see MDN on using CSS custom properties):
- Names start with two dashes and are case sensitive:
--color-actionand--Color-actionare two different properties. - Declared on
:root, they are available to the whole document. - They inherit, so a component or a theme can redefine a token for everything inside it.
var()works in property values only, not in selectors, property names or media query conditions.
Use two layers: primitive and semantic
The most useful structure for an agent has two layers.
- Primitive tokens name the raw palette and scales:
--blue-600,--gray-100,--space-4. - Semantic tokens name a role:
--color-action,--color-surface,--color-text,--color-danger. They point to primitives.
Components use semantic tokens only.
:root {
/* primitives */
--blue-600: #0011ff;
--gray-0: #ffffff;
--gray-900: #1e1e1e;
--red-600: #d92d20;
/* semantic */
--color-action: var(--blue-600);
--color-surface: var(--gray-0);
--color-text: var(--gray-900);
--color-danger: var(--red-600);
}
This matters more for an agent than for a person. Asked to style a “Delete” button, an agent that sees both --red-600 and --color-danger picks the role, and your next rebrand changes one line instead of forty files.
Name tokens so the agent guesses right
An agent chooses a token by its name. Names that describe the role, follow one pattern and form a short scale leave little room for a wrong guess.
| Category | Pattern | Examples |
|---|---|---|
| Color | --color-{role} or --color-{role}-{state} |
--color-action, --color-action-hover, --color-border |
| Spacing | --space-{step} on one scale |
--space-1 (4px) to --space-8 (32px) |
| Radius | --radius-{size} |
--radius-sm, --radius-md, --radius-full |
| Type | --font-size-{step}, --font-weight-{name} |
--font-size-sm, --font-weight-bold |
| Elevation | --shadow-{level} |
--shadow-1, --shadow-2 |
Three habits keep the list usable:
- Name the role, not the look.
--color-dangersurvives a palette change.--reddoes not. - Keep scales short. Eight spacing steps are easier to choose from than twenty. If a design needs 13 px, the design changes, or you add a step on purpose.
- One token per meaning. Two tokens with the same value and the same role make the agent pick one at random.
Spacing tokens pay off twice: they are also the values of gap and padding when you translate Figma auto layout to CSS flexbox.
Themes, fallbacks and quiet failures
Semantic tokens make themes cheap: redefine the roles, keep the components.
@media (prefers-color-scheme: dark) {
:root {
--color-surface: var(--gray-900);
--color-text: var(--gray-0);
}
}
[data-theme="dark"] {
--color-surface: var(--gray-900);
--color-text: var(--gray-0);
}
Two details catch people, and agents, out.
Fallbacks hide mistakes. var(--color-action, #0011ff) uses the second value when the property is not defined. That hides a missing token instead of revealing it, and it copies the palette into components. Avoid fallbacks inside components.
Wrong values fail quietly. If --color-action holds 16px by mistake, color: var(--color-action) does not fall back to an earlier rule. The declaration is invalid at computed-value time, and the property gets its inherited or initial value. Registering the token with @property and a syntax such as "<color>" gives it a type and an initial value to use instead.
Write the rule where the agent reads it
A token file the agent never opens changes nothing. State the rule in the instructions your agent loads. In Claude Code, a rule file in .claude/rules/ with a paths field loads only when the agent works on matching files, which suits UI conventions:
---
paths:
- "src/**/*.{css,tsx}"
---
# Design tokens
- Tokens live in src/styles/tokens.css. Read it before styling anything.
- Components use semantic tokens only (--color-*, --space-*, --radius-*, --font-size-*).
- Never write a hex, rgb() or px value in a component. If no token fits,
stop and ask, or propose a new token in tokens.css in the same change.
- Hover, focus and disabled styles use the matching -hover, -focus and -disabled tokens.
“Propose a new token in the same change” gives the agent a legitimate way out. Without it, an agent that cannot find a fitting token tends to write the raw value and move on. The design handoff guide for AI agents covers the other rules worth writing down.
Check that the agent followed them
Rules drift unless something checks them. Two cheap checks:
-
A search for raw values in components, which you can ask the agent to run before it reports a change. It may flag a few false positives, which is fine for a review:
grep -rnE "#[0-9a-fA-F]{3,8}|rgba?\(" src/components -
A lint rule in your CSS linter. Stylelint’s
color-no-hexrule, for example, rejects hex colors, so the agent sees the error in the same run as your other checks.
In the agent’s report, ask for the list of tokens it added. A new token is a design decision, and you want to see it.
Tokens when you edit a design visually
Tokens also decide where a visual edit lands in the code. If you change a button’s background from #0011FF to #1E1E1E in a design tool, the agent has to decide: is this a new value for --color-action, a new variant of the button component, or a one-off for this screen? Each answer changes a different file.
In agentappbuilder, you can edit a prototype frame directly on the canvas (fill, stroke, corner radius, padding, auto layout, text), and your edits become a new version with the exact list of changes: element, property, value before and after. When you ask your agent to implement them, it applies each change where it belongs in your code (a design token, a component or only this instance) and asks you when the scope is not clear. Implement your edits shows the prompt, and edit a frame lists the properties you can change.
A tip for any prototype: ask your agent to declare the same tokens at the top of each prototype, in a <style> block on :root. The prototype then uses the same names as your code, and a change in one is easier to carry over to the other.
Summary
Declare tokens as CSS custom properties on :root, split them into primitives and semantic roles, name them by role on short scales, and let components use roles only. Then write the rule where your agent reads it, give it a way out when no token fits, and check for raw values before a change is reported as done.
Frequently asked questions
How do you define design tokens with CSS variables?
Declare them as custom properties on :root, for example --color-action: #0011ff, and use them with var(--color-action). Custom property names are case sensitive and inherit by default.
Why does my AI agent still write hex values?
Usually the rule is not written where the agent reads it, or no token fits. Put the rule in the repository instructions, keep the token list short, allow the agent to propose a new token, and add a lint rule that rejects raw colors.
Can I use CSS variables in media queries?
Not in the condition. var() only works in property values, so @media (min-width: var(--bp)) does not work. You can redefine tokens inside a media query instead.
Try it on your own app
Tell Claude Code one sentence. It creates the project and gives you a link to see your screens.
Tell Claude Code:
Connect this project to agentappbuilder.com and show me its screens.