Design tokens are context for your coding agent

July 2026

A design system is discipline. In a team it is a shared vocabulary so five people ship one product. Solo it does the same job against your future self: the you of next month makes the same choices as the you of today. Either way the goal is the same: one product, not a collection of screens.

What changed is who reads it. An agent now writes a lot of the code, and the agent does not read your styleguide. It reads your token file. Every token is an instruction: define --subtext and the agent uses it everywhere helper text appears. Define nothing and it invents a new gray on every prompt. I know because I counted: one small site of mine had quietly accumulated sixteen distinct hex values, and four of them were nearly identical light fills.

Every product I build gets its own design system. The stacks change, the code never transfers. What survives is the method.

The method, surviving a stack change: the same Subtext token as a Kotlin object, a Tailwind @theme entry and a CSS custom property.

Roles, never values

The one principle behind everything: name things for what they are for, never for what they look like. Maintext, not gray-900. Module, not light-grey.

Text hierarchy is alpha steps on one ink, not new grays:

--ink: #212121;
--subtext: rgba(33, 33, 33, 0.6);  /* helper copy, secondary lines */
--faint: rgba(33, 33, 33, 0.4);    /* de-emphasized, timestamps */

One source of truth, automatic harmony. On a dark background the same trick runs with white: one of my apps covers every text tone with eight opacities.

The ink ramp: one color stepped through thirteen alphas covers every text tone in the product.

Lines and fills share the same alphas but get separate names. From a comment that has been sitting in one of my codebases for months: “a border and a background are different intentions, and bg-[var(--line-subtle)] reads wrong even when the value is right.”

Same alpha, different intention: the identical value used as a border token and as a fill token, side by side.

And roles survive platform changes. Body exists in the mobile app (SF Pro, 16px, tight) and on the web (a chosen font, 14px, line-height 1.7). The name is stable; the values are platform specific. Ship the same app on Android and iOS and the role does the same work: one system, one vocabulary, per-platform values. That is exactly why the method survives stack changes.

One role, two value sets: Body resolves to SF Pro 16 on mobile and to a web font at 14/1.7 on the web. The name never changes.

The industry mostly builds this differently. The W3C token spec, Material and Salesforce all keep a core tier of value-named tokens (blue-500) with semantic tokens on top. That layer solves a real problem: multi-brand theming at scale, where forty semantic tokens remap the moment the brand switches. I skip it because I do not have that problem. My dark app and my light SaaS are different brands with different token files, not one file with two mappings. If I ever need real theme swapping, this is the chapter I rewrite first.

The rules that survived

A color earns a ramp only if it is interactive. Brand and error get hover and pressed steps because people click them. Neutrals sit still, so they stay single values.

The second time an exception appears, it becomes a token. Once is an edge case, twice is a pattern.

And the system is a guideline, not a law. It is a generalization, and generalizations meet edge cases they cannot cover. That is expected, not a failure. Break out consciously: locally, with a comment, so the exception stays visible instead of quietly becoming a second system.

One nuance since agents entered the picture: the system is a guideline for me and a law for the agent. An agent follows “never” better than “usually”, so the token file states its rules without exceptions. Breaking out stays a human decision.

Build up or narrow down

Compose hands you a blank slate. No default spacing, no palettes. The risk is never building the system at all and letting values scatter through components. Tailwind ships batteries: twenty-two hues in eleven steps, ready to use. The risk is using them and shipping something that looks like every other Tailwind project.

Opposite work, same destination: one small set of roles. Blank slate means build up. Batteries included means narrow down.

Numbers from a real audit

One of my products had ten border radii in the code. Counting usage told a different story: 8, 10 and 12px carried 54, 45 and 45 uses. Neighbours fold together: 5 into 4, 9 into 8, 18 and 20 into 16. Not because the difference is strictly invisible. Put two cards side by side and 16 versus 20 can be told apart. But the product never asks anyone to make that comparison, and the difference carries no intention: nobody chose 18 over 16 to say something. A scale entry has to mean something. These did not. The scale came out of usage, not theory.

The radius audit: usage counts per value (8, 10 and 12px dominate) and the fold arrows collapsing near neighbours into the survivors.

Same story with the sixteen hex values on the small site: they fold to about seven roles. The heuristic that falls out of both: a value that appears twice is a token candidate. It is the same rule as the exception promotion, applied backwards.

To be fair, usage-first only works when there is usage. On a blank project there is nothing to count, so I start from defaults, which is a small dose of the theory-first approach I just argued against. The difference is size: a minimal set that grows by the promotion rule, not a full palette waiting to be used.

Sixteen scattered hex values clustered into seven roles. Swatches grouped, near-duplicates folded.

The distilled version

I distilled all of this into a Claude Code skill, token-system. It handles three starting points (blank project, an existing codebase with scattered values, a design system living in Figma), asks its gate questions before writing a single token, proposes strong defaults, and asks where the decision belongs to you instead of guessing. I built it because I will need it a fourth time. The current version is on GitHub, linked at the end of this piece. Treat it as young: everything here comes from three projects built by one person in one year. It is battle-tested, but only in my battles, and a method that works for a solo builder can fail in a team of ten in ways I have not seen yet.

Why this matters now

Clarity is not aesthetics. A clear system makes every future session faster, whether the reader is a new teammate or an agent with a fresh context window. Name things for what they are for. Everything else follows.

Check out token-system on GitHub.