Design Tokens for AI Agents: A Practical Architecture Guide
Ask an AI agent to build a button, then a card, then an input field. Check the colors. There will be three different shades of blue, none of which match your brand. This is not an AI problem—it is a token architecture problem.
When AI tools like Claude Code or Cursor generate UI, they have no access to your design decisions. They pattern-match from millions of public repositories and produce plausible-but-wrong values. The fix is giving the AI a machine-readable source of truth it can actually query.
Why Tokens in Figma Are Not Enough
Figma variables are invisible to AI code generators. When a developer inspector-panels a color value and hardcodes #4F46E5, the AI learns that pattern. When you ask the AI to generate the next component, it guesses in the same way—picking whatever hex value felt right in similar open-source projects.
The result is a codebase where blue-600, indigo-500, and #3b82f6 coexist as "the primary color." Every AI generation adds more drift.
Tokens in code change this. When the AI can import and read a structured token file, it stops guessing and starts referencing. The architecture matters more than the values themselves.
Three-Tier Token Structure
The most effective pattern for AI readability uses three layers: primitives, semantic tokens, and component tokens.
Tier 1: Primitives
Primitives are raw values—the full color palette, the type scale, the spacing steps:
// tokens/primitives.ts
export const primitives = {
color: {
blue: {
500: 'oklch(0.55 0.15 250)',
600: 'oklch(0.48 0.16 250)',
},
neutral: {
50: 'oklch(0.98 0 0)',
900: 'oklch(0.20 0 0)',
950: 'oklch(0.15 0 0)',
},
},
spacing: {
2: '0.5rem', // 8px
4: '1rem', // 16px
6: '1.5rem', // 24px
},
}
AI can see the scale and understand the relationship between values. It knows blue.600 is darker than blue.500.
Tier 2: Semantic Tokens
Semantic tokens map primitives to intent. This is the most important layer for AI because names communicate when to use a value, not just what it is:
// tokens/semantic.ts
export const semantic = {
color: {
interactivePrimary: primitives.color.blue[500],
interactivePrimaryHover: primitives.color.blue[600],
interactiveDisabled: primitives.color.neutral[300],
backgroundPrimary: primitives.color.neutral[50],
backgroundInverse: primitives.color.neutral[950],
textPrimary: primitives.color.neutral[900],
textSecondary: primitives.color.neutral[600],
textInverse: primitives.color.neutral[50],
},
spacing: {
insetSmall: primitives.spacing[2],
insetMedium: primitives.spacing[4],
insetLarge: primitives.spacing[6],
},
}
When the AI needs a color for a primary action, it reasons about the name interactivePrimary rather than picking from a list of hex values. This is the mechanism that prevents hallucination.
Tier 3: Component Tokens
Component tokens give the AI exact answers for specific use cases:
// tokens/components.ts
export const components = {
button: {
primaryBackground: semantic.color.interactivePrimary,
primaryBackgroundHover: semantic.color.interactivePrimaryHover,
primaryText: semantic.color.textInverse,
primaryPaddingX: semantic.spacing.insetMedium,
primaryPaddingY: semantic.spacing.insetSmall,
disabledBackground: semantic.color.interactiveDisabled,
disabledText: semantic.color.textSecondary,
},
card: {
background: semantic.color.backgroundPrimary,
padding: semantic.spacing.insetLarge,
},
}
When generating a button, the AI queries components.button.* and gets exact values. No ambiguity. No invented colors.
Using CSS Variables Correctly
FramingUI tokens are CSS variables, not static JS objects with hex values. The correct pattern looks like this:
// tokens reference CSS custom properties
export const tokens = {
color: {
primary: 'var(--foreground-accent)',
textPrimary: 'var(--foreground-primary)',
bgCanvas: 'var(--background-page)',
},
}
Always include a fallback so the UI does not break if the CSS variable is missing:
export const tokens = {
color: {
primary: 'var(--foreground-accent, oklch(0.55 0.15 250))',
},
}
Preventing Hallucination at the Prompt Level
Token architecture is infrastructure. Prompt patterns are how you activate it.
Add a token reference block to your AI agent's system prompt or CLAUDE.md:
When generating UI components, always import from @/tokens.
Use semantic.color.interactivePrimary for primary actions.
Use semantic.color.textPrimary for body copy.
Use semantic.spacing.insetMedium for default padding.
Never hardcode hex values or Tailwind color classes.
This instruction, combined with the token file the AI can read, produces consistent code across every generation session.
Connecting Tokens to AI Through MCP
FramingUI ships an MCP server that exposes design tokens directly to Claude Code. Configure it in .mcp.json at your project root:
{
"mcpServers": {
"framingui": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@framingui/mcp-server@latest"]
}
}
}
The fastest way to set this up is with the init command, which wires the full runtime contract—provider bootstrap, CSS imports, MCP config, and an AI workflow guide—in one step:
npx -y @framingui/mcp-server@latest init
After restarting Claude Code, the AI has structured token access and generates code that references your actual design system.
Common Token Pitfalls
Over-tokenization. Creating a token for every CSS property on every component state makes the system unwieldy. If you have 50 button tokens, AI will get confused by the options. Keep component tokens to the critical decisions: background, text color, padding, disabled state.
Mixing tiers. Referencing primitives directly in component code bypasses the semantic layer. When blue[500] appears in a button component instead of interactivePrimary, AI loses the intent signal and starts interpolating.
Inconsistent naming. Names like colorMain, spacingDefault, and sizeNormal tell the AI nothing about when to use each value. Names like interactivePrimary, insetMedium, and buttonHeightDefault give the AI enough context to make correct choices without additional instruction.
TypeScript as Enforcement
TypeScript types prevent both AI and human developers from referencing tokens that do not exist:
export type ColorToken = `var(--${string})`
export type SpacingToken = `var(--spacing-${string})`
export const tokens: {
color: Record<string, ColorToken>
spacing: Record<string, SpacingToken>
} = {
color: {
primary: 'var(--foreground-accent)',
},
spacing: {
medium: 'var(--spacing-4)',
},
}
When the AI generates a token reference that does not match this type signature, TypeScript flags it immediately. The feedback loop stays tight.
What Good Looks Like
Before token architecture, an AI-generated button:
// hallucinated values, inconsistent with the rest of the app
<button className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg">
Submit
</button>
After:
import { components } from '@/tokens/components'
<button
style={{
background: components.button.primaryBackground,
color: components.button.primaryText,
padding: `${components.button.primaryPaddingY} ${components.button.primaryPaddingX}`,
}}
>
Submit
</button>
Every button the AI generates now references the same tokens. The result is identical to what a human developer would produce following the design system—because they are both reading from the same source of truth.
The investment is a few hours of token architecture work. The return is AI-generated UI that does not require manual correction on every generation.