AI-Native DesignDeep Dives

Token-Based Theming: Why It Matters for AI-Generated UI

CSS variables as the contract between human designers and AI agents.

The Prompt Engineering Project February 23, 2025 11 min read

Quick Answer

Design token theming uses a layered token architecture to support multiple themes from a single design system. Primitive tokens define raw values like colors and spacing. Semantic tokens map those primitives to purposes like background-primary or text-muted. Themes override the semantic layer while keeping component code unchanged, enabling dark mode, brand variants, and white-label products.

When an AI agent generates a user interface, it needs a contract. Not a loose suggestion of how things should look, but a rigid, machine-readable specification that defines every visual decision in the system. Without this contract, the agent makes its own choices -- and those choices will be inconsistent across components, pages, and sessions. The result is a UI that looks like it was built by a different designer every time.

Design tokens are that contract. They are named values that represent the atomic decisions of a visual system: colors, type sizes, spacing increments, border radii, shadows, transition durations. When implemented as CSS custom properties, they become the single source of truth that both human designers and AI agents reference when making any visual decision. The token says "this is the primary brand color." The component says "use the primary brand color here." Neither the human nor the agent needs to know the hex value. They need to know the name.

Why CSS Custom Properties

CSS custom properties are the right abstraction for design tokens because they satisfy four requirements that matter for AI-generated interfaces. Each of these requirements eliminates a class of problems that other approaches leave unsolved.

1

Machine-readable

An AI agent can parse a CSS file, read every custom property declaration, and understand the available visual vocabulary. The format is structured, predictable, and requires no special tooling to interpret. A property named --color-primary communicates its purpose through its name alone.

2

Cascading

CSS custom properties inherit through the DOM tree. Set a token on the root element and every descendant can reference it. Override it on a specific container and everything inside that container uses the override. This is how themes work: a dark mode class on the root redefines color tokens, and every component that references those tokens updates automatically.

3

Runtime swappable

Changing a custom property value in JavaScript updates every element that references it, immediately, without a rebuild. This enables live theme switching, user preference adaptation, and dynamic brand customization -- all at runtime, all without touching component code.

4

Single source of truth

One file defines all tokens. Components reference tokens, not raw values. When the brand color changes, you update one line in one file. Every component across every page reflects the change. There is no search-and-replace across hundreds of files.

Design tokens are not about colors. They are about giving every visual decision a name, so that machines and humans can reference the same vocabulary.

The Token Architecture

A well-structured token system has layers. The foundation layer defines raw values -- the specific hex codes, pixel sizes, and millisecond durations that make up the visual palette. The semantic layer maps those raw values to purpose -- this color is for primary actions, this spacing is for section gaps, this duration is for micro-interactions. The component layer maps semantic tokens to specific component properties -- the button background uses the primary action color, the card padding uses the section spacing.

tokens/foundation.css
:root {
  /* ── Color Palette ─────────────────────── */
  --gray-50: #fafafa;
  --gray-100: #f4f4f5;
  --gray-200: #e4e4e7;
  --gray-300: #d4d4d8;
  --gray-400: #a1a1aa;
  --gray-500: #71717a;
  --gray-600: #52525b;
  --gray-700: #3f3f46;
  --gray-800: #27272a;
  --gray-900: #18181b;
  --gray-950: #09090b;

  --blue-500: #3b82f6;
  --blue-600: #2563eb;
  --green-500: #22c55e;
  --red-500: #ef4444;
  --amber-500: #f59e0b;

  /* ── Typography Scale ──────────────────── */
  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.25rem;
  --font-size-2xl: 1.5rem;
  --font-size-3xl: 1.875rem;
  --font-size-4xl: 2.25rem;

  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;

  --line-height-tight: 1.25;
  --line-height-normal: 1.5;
  --line-height-relaxed: 1.75;

  /* ── Spacing Scale ─────────────────────── */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-12: 3rem;
  --space-16: 4rem;
  --space-24: 6rem;

  /* ── Motion ────────────────────────────── */
  --duration-fast: 100ms;
  --duration-normal: 200ms;
  --duration-slow: 400ms;
  --easing-default: cubic-bezier(0.4, 0, 0.2, 1);
  --easing-in: cubic-bezier(0.4, 0, 1, 1);
  --easing-out: cubic-bezier(0, 0, 0.2, 1);
}

The foundation layer is intentionally abstract. These are the raw materials. They carry no opinion about how they should be used. The semantic layer is where design intent enters the system.

tokens/semantic.css
:root {
  /* ── Semantic Colors ───────────────────── */
  --color-background: var(--gray-50);
  --color-surface: #ffffff;
  --color-surface-raised: var(--gray-100);
  --color-text-primary: var(--gray-900);
  --color-text-secondary: var(--gray-500);
  --color-text-muted: var(--gray-400);
  --color-border: var(--gray-200);
  --color-border-strong: var(--gray-300);

  --color-action-primary: var(--blue-600);
  --color-action-primary-hover: var(--blue-500);
  --color-action-text: #ffffff;

  --color-success: var(--green-500);
  --color-error: var(--red-500);
  --color-warning: var(--amber-500);

  /* ── Semantic Typography ───────────────── */
  --text-heading: var(--font-size-3xl);
  --text-subheading: var(--font-size-xl);
  --text-body: var(--font-size-base);
  --text-caption: var(--font-size-sm);
  --text-overline: var(--font-size-xs);

  /* ── Semantic Spacing ──────────────────── */
  --spacing-section: var(--space-16);
  --spacing-element: var(--space-6);
  --spacing-inline: var(--space-3);
  --spacing-compact: var(--space-2);

  /* ── Semantic Motion ───────────────────── */
  --transition-interaction: var(--duration-fast) var(--easing-default);
  --transition-layout: var(--duration-normal) var(--easing-out);
  --transition-entrance: var(--duration-slow) var(--easing-out);
}

Dark Mode as Token Override

Dark mode is the canonical proof that token-based theming works. When your components reference semantic tokens instead of raw values, switching to dark mode requires only redefining the semantic layer. No component code changes. No conditional logic. No class swapping on individual elements. You override the tokens, and the entire interface updates.

tokens/dark-theme.css
[data-theme='dark'] {
  --color-background: var(--gray-950);
  --color-surface: var(--gray-900);
  --color-surface-raised: var(--gray-800);
  --color-text-primary: var(--gray-50);
  --color-text-secondary: var(--gray-400);
  --color-text-muted: var(--gray-500);
  --color-border: var(--gray-700);
  --color-border-strong: var(--gray-600);

  --color-action-primary: var(--blue-500);
  --color-action-primary-hover: var(--blue-600);
}

/* Theme switch in JavaScript: one line */
/* document.documentElement.dataset.theme = 'dark' */

This is not just convenient -- it is architecturally correct. The component that renders a card does not need to know whether the page is in light mode or dark mode. It references var(--color-surface) for its background and var(--color-border) for its border. The token system resolves those references to the correct values based on the current theme. The component is theme-agnostic. The tokens are theme-aware. Responsibilities are correctly separated.

Test your dark mode by searching your component CSS for any raw color value -- hex codes, rgb(), hsl(). Every one of those is a token that should exist but does not. If a component uses a raw value, it will not respond to theme changes.

How AI Agents Use Tokens

This is where the investment in token architecture pays its largest dividend. When you ask an AI agent to generate a new component, the agent needs to know what visual vocabulary is available. Without tokens, you provide a vague instruction like "match our brand style" and hope the agent guesses correctly. With tokens, you provide a machine-readable specification: here are the colors, the spacing increments, the type sizes, and the motion durations. Use only these values.

The token file becomes part of the agent's context. You include it in the system prompt or inject it as reference material. The agent reads the available tokens and constrains its output to only use declared values. The result is a generated component that is visually consistent with every other component in the system, because it draws from the same vocabulary.

agent-prompt-with-tokens.ts
const systemPrompt = `You are a UI component generator.
You MUST use only the design tokens defined below.
Never use raw color values, pixel sizes, or duration values.
Reference tokens using var(--token-name) syntax.

Available tokens:
${tokenFileContents}

Rules:
- Backgrounds: use --color-background, --color-surface, or --color-surface-raised
- Text: use --color-text-primary, --color-text-secondary, or --color-text-muted
- Spacing: use --spacing-section, --spacing-element, --spacing-inline, or --spacing-compact
- Transitions: use --transition-interaction for hover/focus, --transition-layout for size changes
- Never invent new tokens. If a token does not exist for what you need, flag it.`

// The agent now generates:
// background: var(--color-surface);
// padding: var(--spacing-element);
// color: var(--color-text-primary);
// transition: background var(--transition-interaction);

// Instead of:
// background: #ffffff;
// padding: 24px;
// color: #18181b;
// transition: background 100ms ease;

A token file in the system prompt is worth a thousand words of style guidance. Machines do not interpret aesthetic direction. They follow specifications.

Tokens and Component Variants

Component variants are where tokens demonstrate their composability. A button component might have three variants -- primary, secondary, and ghost -- each defined entirely through token references. The variant does not change the component's structure. It changes which tokens the component references for color, background, and border.

components/button.css
.button {
  padding: var(--spacing-compact) var(--spacing-element);
  font-size: var(--text-body);
  font-weight: var(--font-weight-medium);
  border-radius: 6px;
  transition: all var(--transition-interaction);
  cursor: pointer;
}

.button--primary {
  background: var(--color-action-primary);
  color: var(--color-action-text);
  border: 1px solid transparent;
}
.button--primary:hover {
  background: var(--color-action-primary-hover);
}

.button--secondary {
  background: var(--color-surface);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border-strong);
}
.button--secondary:hover {
  background: var(--color-surface-raised);
}

.button--ghost {
  background: transparent;
  color: var(--color-text-secondary);
  border: 1px solid transparent;
}
.button--ghost:hover {
  color: var(--color-text-primary);
  background: var(--color-surface-raised);
}

When an AI agent generates a new component, it can follow this same pattern: define the structural styles once and create variants by swapping token references. The agent does not need to understand color theory or visual hierarchy. It needs to understand which tokens exist and which combinations are appropriate for each variant. This is a constraint that produces consistency, not a limitation that restricts creativity.

Comparing Approaches for AI-Generated UI

Token-based theming is not the only way to constrain AI-generated interfaces. Utility class systems like Tailwind and inline styles are alternatives. Each has different characteristics when an AI agent is the one writing the code.

Instead of

Inline styles: style={{ color: '#18181b', padding: '24px' }}. No theming. No dark mode. No consistency between components. The AI agent must memorize raw values and apply them perfectly every time.

Try this

Token-based: var(--color-text-primary), var(--spacing-element). Full theming. Automatic dark mode. Consistency is guaranteed by the system, not the agent.

Utility classes occupy a middle ground. Tailwind's class names like text-gray-900 and p-6 are effectively token references with a different syntax. An AI agent can use them consistently, and they constrain the available values. However, they mix design decisions into the markup rather than separating them into a standalone specification. They also require build-time processing, which means you cannot swap values at runtime.

For AI-generated interfaces, the strongest approach is to use CSS custom properties as the token system and let the agent reference those tokens in whatever styling approach your project uses -- plain CSS, CSS modules, or even utility classes that map to token values. The tokens are the source of truth. The styling approach is an implementation detail.

If your project uses Tailwind, consider extending the Tailwind configuration to reference your CSS custom property tokens. This gives you the ergonomics of utility classes with the flexibility of runtime token swapping.

Design tokens are infrastructure, not decoration. They are the foundation that makes consistent AI-generated interfaces possible at scale. Without them, every generated component is a one-off that requires manual adjustment to match the rest of the system. With them, the system itself enforces consistency, and the agent simply follows the specification.

The investment is small: one file of custom property declarations, a semantic naming convention, and a dark mode override. The return is an interface that looks intentional regardless of whether a human or a machine built any given component. That is what a design system is for.

Key Takeaways

1

Design tokens are named values that represent every visual decision in a system. Implemented as CSS custom properties, they become the single source of truth for both human designers and AI agents.

2

CSS custom properties are machine-readable, cascading, runtime-swappable, and centralized. These four properties make them the correct abstraction for token-based theming.

3

Dark mode is a token override, not a component-level concern. Redefine the semantic layer and every component updates automatically.

4

AI agents use token files as a machine-readable specification. Including the token file in the system prompt constrains the agent to produce visually consistent output.

5

Token-based theming is stronger than inline styles (no theming, no consistency) and more flexible than utility classes alone (no runtime swapping). Use tokens as the source of truth regardless of your styling approach.

Frequently Asked Questions

Common questions about this topic

Component Design for Conversational InterfacesThe AI Search Stack: How to Build Search That Actually Works

Related Articles

AI-Native Design

Why We Built a Design System for an AI Project

Most AI products look the same. We built a full design system with tokens, components, and principles to prove they don'...

AI-Native Design

Dark Mode Isn't Optional: Designing AI Interfaces for Real Use

Why dark mode is the default for professional tools. Accessibility, cognitive load, and how to implement both modes with...

AI-Native Design

Building a Design System Documentation Site

How we built the PEP /design section. Architecture decisions, component documentation patterns, and live preview impleme...

All Articles