# Design tokens an agent can read

> A hex value carries no intent. blue-500 tells an agent nothing about when to use it. color.action.primary tells it everything.

Source: https://umairalikhatri.com/work/design-tokens-an-agent-can-read/
Author: Umair Ali Khatri
Updated: 2026-08-29

---

[&larr; Back to work](https://umairalikhatri.com/work/)
agent-ready

# Design tokens an agent can read

29 August 2026 · 5 min read

A hex value carries no intent.

Your build pipeline does not mind. It needs #1A1C1E and a variable to hang it on. It resolves the reference, writes the CSS, and ships.

An agent minds. It has to decide where the colour goes. blue-500 gives it a position on a ramp and nothing else. color.action.primary gives it a job.

Same file. Two different artifacts.

## The three tiers

Most token files have three layers, whether or not anyone named them.

Raw. The value. #D4FF27. 16px. 600. No opinion, no context.

Semantic. The value plus a job. action-primary. section-gap. This tier says what the token is for.

Component. The job applied to one thing. button-primary.backgroundColor. Narrow on purpose.

A pipeline runs on the raw tier alone. It maps names to values and stops.

An agent cannot. It needs the semantic tier, and it needs that one most.

The raw tier tells it what exists. The component tier tells it what has already been decided. The semantic tier is the only one that helps with a case nobody decided yet.

## The test

Ask an agent for a financial dashboard palette. Give it no values.

It has nothing of yours to apply, so it infers from training data. It returns the average of every dashboard it has read, which is not yours.

The mechanism holds either way. With no values in the file, the agent is not applying your system. It is approximating a house style from memory.

Now give it action-primary: "#B8422E" and one line saying this is the only colour a user may click. The guess is gone. The decision now lives in your file instead of in its weights.

Semantic naming stops being a style preference at that point. It is the difference between an agent applying your system and an agent inventing one.

## What a token needs

A value is a third of a token. The other two thirds usually go missing.

A name that states intent. action-primary, not lime. Not brand-2. The name is the instruction. Everything downstream reads it before the value.

A rule for when it applies. One sentence. "Primary actions only. One per view." An agent will honour a constraint it can read. It cannot honour one that lives in a Figma comment.

A stated contrast pairing wherever it carries text. Not a ratio for the colour on its own. No such thing exists. Contrast is a property of a pair. Name the foreground token, state the measured ratio, date it.

[DESIGN.md](https://umairalikhatri.com/work/what-is-design-md/) carries the first two well. The YAML frontmatter holds the tokens and the markdown body below holds the reasoning. The spec is direct about the split:

The tokens are the normative values; the prose provides context for how to apply them.

The third one is on you. The frontmatter keys are version, name, description, omitted, colors, typography, rounded, spacing and components. Only name is required. There is no contrast field. So the pairing goes in the prose body, next to the token that needs it.

## The block

Here is the shape. Semantic names in colors, component tier referencing the tier above it.

---
version: alpha
name: Ledger
description: Dense financial UI. One action colour. High-contrast surfaces.
colors:
primary: "#1A1C1E"
surface: "#FFFFFF"
action-primary: "#B8422E"
on-action-primary: "#FFFFFF"
accent-signal: "#D4FF27"
typography:
body-md:
fontFamily: Public Sans
fontSize: 16px
fontWeight: 400
lineHeight: 1.6
rounded:
md: 8px
spacing:
base: 16px
md: 16px
lg: 32px
components:
button-primary:
backgroundColor: "{colors.action-primary}"
textColor: "{colors.on-action-primary}"
rounded: "{rounded.md}"
padding: 12px
---

Then the rules go under ## Colors in the body, where the agent will read them.

- action-primary — primary actions only. One per view. Carries on-action-primary text at 5.44:1. Passes AA.

- primary — body text on surface at 17.09:1. Passes AAA.

- accent-signal — fills and borders only. Never text on surface. That pair measures 1.16:1 and fails everything. On primary it reads at 14.76:1.

Six lines. Now the agent knows the ramp, the job, and the boundary.

## The table that lied

Accessibility belongs in the token, not in a review step at the end.

I have seen a design system that documented #D4FF27 on white as 5.2:1 and marked it as passing WCAG AA. The measured ratio is 1.16:1. The same table gave black on white as 14.5:1. Black on white is 21:1, the highest contrast that exists.

Four lines under that table, the same document said this:

Do NOT use: Lime text on light backgrounds (fails WCAG AA)

A pass and a fail for the same pairing on the same page.

The number was never load-bearing. Nobody ran it. It sat beside a checkmark as decoration, and every human reader skimmed past it because the swatch looked fine.

An agent does not skim. It reads the number, believes it, and ships the failure with confidence. Your file said pass.

So measure the pairs you publish. Write the date you measured them. A token that can hold text has a partner and a ratio, or it is not finished.

## The ones with no value

Some tokens have no number. Motion feel is the obvious one. Density is the other.

You cannot write "restrained" as a duration. You can write the duration, then write the rule that produced it.

duration-fast: 120ms is half a token. The other half reads: nothing moves more than it has to, entrances are faster than exits, no easing that overshoots. One half is the value. One half is the intent.

The intent is what an agent falls back on when it hits a case your file did not anticipate. Without it, the fallback is training data.

Density works the same way. spacing.base: 16px does not tell anyone your tables run tight and your marketing pages run loose. A sentence in the prose does.

This is why the format is markdown with frontmatter and not a JSON blob. Values on top. Judgment underneath. An agent reads both, and a pipeline ignores the half it does not need.

## The check

Open your token file. Read one name out loud without looking at the value.

If you cannot say what it is for, an agent cannot either.

Token naming is the smallest part of making a system [agent-ready](https://umairalikhatri.com/work/agent-ready-design-systems/), and the first part to fail. It is also the cheapest to fix. Renaming costs an afternoon. The rules cost another one. The measurements cost less than that, and they are the ones that keep you honest.

The rest of [my work](https://umairalikhatri.com/work/) sits on top of this.

A pipeline needs the value. An agent needs the reason. Write both.

Every contrast ratio here was measured with the WCAG 2.1 relative luminance formula on 29 August 2026. Check my numbers.
[Back to work](https://umairalikhatri.com/work/)
