# Design Tokens as AI Guardrails: Keeping AI-Generated Screens On-Brand

> Source: <https://dev.to/davekurian/design-tokens-as-ai-guardrails-keeping-ai-generated-screens-on-brand-59n9>
> Published: 2026-07-08 15:05:35+00:00

The coding agents got good at layout. Ask Cursor, Claude Code, or Lovable for a pricing card and you get a recognisable, mostly-correct row of features with a button on the bottom — in about three seconds. The thing they're not good at is the part underneath: which blue, how much spacing, which grey for borders, what radius pairs with what shadow. They pick from an infinite palette and they pick badly.

A constrained token set is the cheapest, most reliable guardrail you can put in front of that. The surprise isn't that tokens help. The surprise is how aggressively they help *the agent* — not the human.

Here's what an unconstrained agent produces for "build me a pricing page" in a fresh project, most of the time:

```
<section className="bg-white">
  <div className="max-w-6xl mx-auto px-4 py-16">
    <h1 className="text-4xl font-bold text-gray-900">Pricing</h1>
    <p className="text-gray-600 mt-2">Choose a plan that fits...</p>

    <div className="grid md:grid-cols-3 gap-6 mt-12">
      <div className="border rounded-lg p-6">
        <h2 className="font-semibold text-xl">Starter</h2>
        <p className="text-3xl font-bold mt-2">$9</p>
        <button className="mt-4 w-full bg-blue-500 text-white rounded-md py-2">
          Choose
        </button>
      </div>
      {/* two more cards */}
    </div>
  </div>
</section>
```

Nothing is *wrong* with it. That's the problem. It is the most-average possible pricing page in 2025 — `bg-blue-500`

, `text-gray-900`

, `rounded-lg`

, `text-3xl font-bold`

. It will sit in your codebase looking fine and meaning nothing. Three months later you have forty-eight slightly different "brand blues" and none of them match.

The agent didn't fail at code. It failed at taste — because taste requires a palette, and the palette wasn't available.

You can leave a `CLAUDE.md`

that says "use our brand colors." The agent will read it, retain most of it, and reach for `bg-blue-500`

on the next prompt anyway. Documents are suggestions; tokens are types.

Prose conventions are unreliable because:

Tokens mean something concrete. There are 12 blue values, and the agent can pick any of them — but not the unbranded default unless you've shipped a token called `brand.600`

that resolves to your actual color.

The mechanism is *reduction*, not instruction. You are not telling the agent to be on-brand; you're removing the off-brand options from the API it consumes.

Treat tokens like a typed API. Each surface — color, spacing, radius, shadow, type scale, motion — is a closed set of named values. The agent picks from the set, the lint enforces the set, and "did I make a design system?" becomes a binary, not a vibe.

| Surface | What it constrains to | Example |
|---|---|---|
| Color | Semantic roles, all themes | `brand.600` |
| Spacing | 4px-grid scale | `space.4` |
| Radius | Stepped, paired with shadow | `radius.md` |
| Type scale | Size + line-height pairs | `text.2xl` |
| Shadow | Elevation steps | `shadow.2` |
| Motion | Durations + easing curves | `motion.fast` |

That's a finite set of *atomic decisions* instead of an infinite one. The agent no longer has to pick a `#3B82F6`

; it has to pick `brand.600`

. The lint no longer has to flag "you used `bg-blue-500`

"; the lint has to flag "you used a value not in the token namespace."

``` js
// @otfdashkit/tokens — what the agent actually imports
import { tokens } from "@otfdashkit/tokens";

tokens.color.bg.surface   // "#FFFFFF" in light, "#0B0B0C" in dark
tokens.color.fg.muted     // muted text colour
tokens.space.4            // 16px on the grid
tokens.radius.md          // the default card radius
tokens.shadow.2           // subtle elevation
```

When an agent is told to build a card, it imports `tokens`

and references `tokens.color.bg.surface`

— not `#FFFFFF`

. The output is theme-aware by construction. The same component drops into a dark theme with no agent involved.

A token file on disk is still a suggestion. The thing that turns tokens into a real guardrail is the lint and the AI-tool configs working together. Three layers:

**Layer 1 — the lint.** A script runs on every generated file and checks that any color literal, spacing value, or radius is either a token reference or a documented exception. Off-token values fail the build. No `bg-[#3B82F6]`

. No `p-[13px]`

. No `rounded-[7px]`

.

**Layer 2 — the AI directives.** A `CLAUDE.md`

and `.cursorrules`

ship with the kit. They don't lecture — they enumerate *which files contain tokens* and *which imports are valid*. The agent consumes them on every prompt:

``` php
<!-- CLAUDE.md fragment -->
- Colors MUST come from `@otfdashkit/tokens`. Never hardcode hex.
- Spacing MUST use the `space.*` scale. No arbitrary `p-[13px]` values.
- Radius MUST use the `radius.*` scale.
- If a needed value is not in the token namespace, ASK before adding it.
- Component imports come from `@otfdashkit/ui` (web) or `@otfdashkit/ui-native` (iOS/Android).
```

The "ASK before adding it" line matters more than it looks. It pushes the agent out of one of its worst behaviours: inventing a new shade because the prompt didn't constrain it.

**Layer 3 — the prompt library.** Twenty-odd tested prompts — "build a pricing page", "redesign this card dark-mode-safe", "add a billing screen with a usage meter" — that reference tokens by name. The agent starts from a working scaffold every time, so its deviation budget is small.

A token that ships in `@otfdashkit/tokens`

has cleared a 24-item design checklist before anyone sees it. The shape of that checklist:

`radius.full`

doesn't pair with `shadow.5`

, because a pill on a modal-level shadow looks wrong, and the rules know it.`prefers-reduced-motion`

via a hook that auto-bypasses.When the agent picks `brand.600`

from that surface, it inherits the audit. That's the part the agent cannot reproduce on its own in a fresh prompt — six months of pairwise decisions about contrast, pairing, and elevation, compressed into a name.

Every few months a new coding model ships, costs less, and gets better at UI. The pattern that doesn't change across model generations is: a constrained, audited token surface, plus a lint that fails on drift, plus a directive file the agent consumes, plus a prompt library that's been tested against real outputs. The model gets faster; the guardrail stays.

This is the layer worth investing in. Specificity beats flexibility. A typed token namespace ages better than a `CLAUDE.md`

paragraph does — three models from now, the paragraph is still prose, but `tokens.color.brand.600`

is still the truth.

For teams shipping AI-generated screens today: the highest-use move isn't a better prompt, it's a token file that fails the build when the agent reaches outside it.

A two-minute setup. Install the token package, point the lint at it, drop the directives into your repo.

```
# 1. Install tokens (no full kit required)
npm i @otfdashkit/tokens
npm i @otfdashkit/ui            # ~200 web components
npm i @otfdashkit/ui-native     # same components for iOS + Android

# 2. Add the token lint to your dev dependencies
npm i -D @otfdashkit/lint-tokens

# 3. Drop the agent directives into your repo root
# (CLAUDE.md, .cursorrules, ai/prompts/* — copy from any OTF kit)
```

Or pull a full kit that ships tokens + lint + directives pre-wired:

```
# SaaS Dashboard — full-stack web, auth + billing + DB + Stripe
# Fitness — one codebase for iOS / Android / web
# Booking — scheduling flow end-to-end
# $99 each, Everything Bundle $149
npx otf-kit          # copy-paste CLI walks you through selection
```

Either path gives you the same outcome: any prompt to your agent of choice ("build a pricing page", "add a billing screen") lands on `tokens.color.brand.600`

, not `bg-blue-500`

. The lint catches anything that drifts. The 24-item checklist already passed for the values the agent is allowed to reach for.

[[COMPARE: unbounded agent output vs token-bound agent output]]

A pricing page that ships in three seconds *and* looks like it came from the same team as the dashboard. A dark mode that "just works" because the token layer flips one theme across web and mobile. A codebase where `grep "bg-blue"`

returns nothing because the agent was never offered the option. A model swap that doesn't reset the design — the next model reads the same tokens and produces the same shade of `brand.600`

.

The agent got better at the layout. The tokens — quietly — got better at everything underneath.
