Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelog
  1. Home
  2. Blog
  3. Building UI with AI agents
  4. Claude Code Design System: Make 50 Screens Match
Auf Deutsch lesen
  1. Blog
  2. Building UI with AI agents
  3. Claude Code Design System: Make 50 Screens Match
Building UI with AI agents
Building UI with AI agents

Claude Code Design System: Make 50 Screens Match

SR
Serban Rusu · Founder of Wingo UI
Oct 16, 2026 · 12 min read

On this page

0%
  1. Why do screens from Claude Code drift apart?
  2. What goes into a Claude Code design system?
  3. Step 1: How do you give Claude Code your design tokens?
  4. Step 2: What should a Claude Code DESIGN.md contain?
  5. Step 3: How do components keep Claude Code UI design consistent?
  6. Step 4: How do you load the rules for every UI task?
  7. Step 5: How do you check that Claude Code followed the design system?
    1. Check every edited file with a hook
    2. Inventory every route
  8. Where should you start?
  9. FAQ
    1. How do I make Claude Code follow my design system?
    2. Should I use the Claude Code frontend design skill for product screens?
    3. What should a DESIGN.md for Claude Code contain?
    4. How do I check that AI-generated screens are consistent?
    5. Is a CLAUDE.md enough on its own?

TL;DR

A Claude Code design system puts exact values where the agent cannot miss them: color tokens in CSS, a DESIGN.md with size, radius and type tables, and components whose props and provider defaults already encode those tables. A short CLAUDE.md and a project skill point Claude Code at the rules. A PostToolUse hook that greps each edited file and a script that counts the buttons on every route catch the drift that gets through.

Published Oct 16, 2026

You asked Claude Code for a settings page on Monday, a billing page on Tuesday and an onboarding flow on Wednesday. Each one looks fine on its own. Side by side, the buttons are three different heights, one page has 8px corners and another 12px, and the billing page found a blue you never picked. A Claude Code design system fixes this by giving the agent the same exact values in every session: tokens, size tables, component rules, and a check that tells it when it drifted.

This tutorial builds that setup for a Next.js and Tailwind CSS v4 app. The examples use Wingo UI's Button, UIProvider and Tones, because we run the same setup in our own repository, where one design file governs a 352-item catalog (as of October 2026). The layout works with any component library. More guides on agents and UI are in Building UI with AI agents.

Why do screens from Claude Code drift apart?

Every session starts without memory of the last one. When a prompt says "a settings page" and nothing in context says how tall a button is, Claude Code picks a reasonable value, and the next session picks another reasonable value. Each choice is defensible. Fifty of them are a product that looks assembled from five kits.

Prose rules help less than people expect. "Keep spacing consistent" names no number, so the agent has nothing to copy. A rule like "buttons are 32, 40 or 48px tall, md by default" gives it one.

Memory files do not close the gap on their own either. Anthropic's Claude Code memory docs (opens in a new tab) (October 2026) say Claude treats CLAUDE.md as context, that specific and concise instructions are followed more consistently, and that a hook is the tool for anything that must happen regardless of what Claude decides. That is why this setup puts the values in code and adds checks.

Many people install the Claude Code frontend design skill first. It is an official plugin in the anthropics/claude-code repository (opens in a new tab) (version 1.1.0 as of October 2026, by Prithvi Rajasekaran and Alexander Bricken), and its description calls it guidance for "distinctive, intentional visual design". Its instructions ask Claude to act as the design lead of a studio, pick a palette and typefaces specific to each brief, and write a plan with four to six named colors before any code. That is the right brief for a new landing page with its own identity. It is the wrong brief for the 37th screen of an app that already has one, and the skill does not cover working inside existing tokens or components. For product screens, your rules have to come from your own files.

What goes into a Claude Code design system?

Five pieces, each in the place where it is hardest to ignore:

  1. Tokens in CSS. Colors, elevation and layers as CSS variables, mapped to Tailwind classes, so the agent writes bg-surface and both themes work.
  2. A DESIGN.md with tables. Sizes, radius per size, type scale and spacing as numbers, plus the prop names every component uses.
  3. Components that carry the tables. The size and radius maps live inside the component, so <Button size="sm"> is right without the agent knowing any pixel value.
  4. Defaults in code. App-wide choices set once in a provider, so the shortest JSX the agent can write is also the correct JSX.
  5. Checks. A hook after every edit and a script across every route, so drift comes back to the agent as an error.

Here is the project layout we will end up with:

text
your-app/
├── CLAUDE.md # @AGENTS.md, nothing else
├── AGENTS.md # short: commands, where things live, "read docs/DESIGN.md"
├── docs/DESIGN.md # the tables
├── app/globals.css # the tokens
├── app/layout.tsx # UIProvider defaults
├── .claude/skills/product-ui/SKILL.md # loads for any UI task
├── .claude/hooks/check-ui.sh # runs after every Edit or Write
├── .claude/settings.json # registers the hook
└── scripts/ui-inventory.ts # buttons per route, across the app

Step 1: How do you give Claude Code your design tokens?

Put them in CSS, where the agent reads them and the browser enforces them. In Tailwind CSS v4 that means plain CSS variables for each theme and an @theme inline block that turns them into utilities. A shortened version of ours, with most tokens and some shadow layers left out:

css
:root {
--background: oklch(1 0 0);
--surface: #ffffff;
--brand: #d63f00;
--elevation-raised: 0 0 0 1px rgb(0 0 0 / 0.07), 0 1px 2px rgb(0 0 0 / 0.05);
--z-popover: 1400;
}
.dark {
--background: oklch(0 0 0);
--surface: #1a1a1a;
--elevation-raised: inset 0 1px 0 0 rgb(255 255 255 / 0.06), 0 0 0 1px rgb(255 255 255 / 0.08);
}
@theme inline {
--color-background: var(--background);
--color-surface: var(--surface);
--color-brand: var(--brand);
--shadow-raised: var(--elevation-raised);
--z-index-popover: var(--z-popover);
}

Now bg-surface shadow-raised is a raised card in both themes and z-popover is a layer with a name. The semantic names matter more than the values: an agent that writes bg-surface cannot pick the wrong gray. The full approach, with dark mode and contrast checks, is in Tailwind v4 design tokens.

Step 2: What should a Claude Code DESIGN.md contain?

Numbers in tables, and the reason behind each rule that looks arbitrary. Our docs/DESIGN.md opens by saying that the numbers are decisions, and that an author who needs something the file does not cover should copy the closest pattern and say so. That one instruction stops most invented values.

The size table is the part that makes screens line up, because a button, an input and a select only match when all three use it:

md
## Sizes
`size="sm" | "md" | "lg"`, default `md`. Every control with a height uses this table.
| | sm | md | lg |
| ---------------- | ----------------------- | -------------------------- | -------------- |
| Height | 32px `h-8` | 40px `h-10` | 48px `h-12` |
| On touch screens | 32px + 44px hit area | 44px `pointer-coarse:h-11` | 48px |
| Padding (button) | `px-3` | `px-4` | `px-5` |
| Font | `text-[13px]` | `text-sm` | `text-[15px]` |
| Icon | `size-3.5` | `size-4` | `size-[18px]` |
Radius per size (`radius` prop, default `md`), for sizes sm / md / lg: `md` 8 / 10 / 12px, `lg` 10 / 14 / 16px, `full` a pill.
## Rules
- Every `tone` defaults to `neutral`. `tone="brand"` is opt-in, at most once per screen, for its main action.
- One solid button per view. Secondary actions use `soft` or `outline`.
- Colored text is `text-<tone>-soft-foreground`, never `text-<tone>`: fills are tuned for fills.

Ours is 614 lines, so the AGENTS.md links it ("Read docs/DESIGN.md before building anything") instead of importing it into every session. How CLAUDE.md, AGENTS.md and imports load is covered in AGENTS.md vs CLAUDE.md.

If you would rather keep tokens in a standard shape, the google-labs-code organization publishes an open DESIGN.md format (opens in a new tab): YAML front matter with colors, typography, rounded and spacing tokens, then Markdown sections for the rationale. Its CLI (npx @google/design.md lint DESIGN.md) reports broken token references and WCAG contrast problems as JSON and exits with code 1 on errors. As of October 2026 the format is at version alpha, so expect field names to change.

Step 3: How do components keep Claude Code UI design consistent?

By owning the tables, so the agent only picks a name. Claude Code UI design gets consistent fastest when size, variant, tone and radius are the only decisions left. In our Button, the size table is a map in the file:

tsx
// 32 / 40 / 48px. on a coarse pointer md grows to 44px and sm keeps its look but gets a 44px hit area
const SIZES: Record<ButtonSize, string> = {
sm: "h-8 gap-1.5 px-3 text-[13px] pointer-coarse:after:absolute pointer-coarse:after:inset-x-0 pointer-coarse:after:-inset-y-1.5",
md: "h-10 gap-2 px-4 text-sm pointer-coarse:h-11",
lg: "h-12 gap-2 px-5 text-[15px]",
"icon-sm": "size-8 pointer-coarse:after:absolute pointer-coarse:after:-inset-1.5",
icon: "size-10 pointer-coarse:size-11",
"icon-lg": "size-12",
};

Colors go through one more layer. Every component with a tone prop paints with five CSS variables (--tone, --tone-foreground, --tone-soft, --tone-soft-foreground, --tone-border) that Tones sets from the design tokens, or from any CSS color through colorVars. A rule in DESIGN.md can then say "tone" and never name a color.

Six tones and one custom color, each painted as a solid fill, a soft tint and an outline from the same five CSS variables. Switch the site theme to see every pair stay readable
$ npx wingo-ui@latest add tones
FreeTones docs

App-wide choices go in the root layout. Every Wingo UI component reads its defaults through useDefaults under its data-slot name, and explicit props still win:

tsx
// app/layout.tsx
import type { ReactNode } from "react";
import { UIProvider } from "@/lib/ui-config";
import "./globals.css";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<UIProvider locale="en-US" defaults={{ button: { radius: "full" } }}>
{children}
</UIProvider>
</body>
</html>
);
}

After that, <Button>Save changes</Button> on any of the 50 screens is pill-shaped, and the agent never had to remember it.

The top button has no provider. The one below sits in a provider with radius full and tone brand, and the pair at the bottom adds a nested provider with size sm that keeps the outer radius and tone
$ npx wingo-ui@latest add ui-config
FreeUIProvider docs

Step 4: How do you load the rules for every UI task?

With a project skill. Claude Code reads skills from .claude/skills/<name>/SKILL.md. Only the description sits in context all the time; the body loads when Claude decides the task matches, or when you type /product-ui. Anthropic's skills docs (opens in a new tab) suggest keeping SKILL.md under 500 lines and moving reference material into separate files, which suits a design system: the skill is the checklist, DESIGN.md is the reference.

md
---
name: product-ui
description: Rules for building or changing any screen, page, form, dialog or component in this app. Use before writing UI code and before reviewing a screen.
---
# Product UI
This app has one design system. Match it; never invent a value.
1. Read the sections of docs/DESIGN.md you need: tokens, sizes, radius, type and spacing, props.
2. Build from components/ui. Configure with props (size, variant, tone, radius, classNames) before adding classes.
3. Do not repeat app-wide defaults (they live in UIProvider in app/layout.tsx). Pass a prop only where one instance differs.
4. Semantic color classes only: bg-background, bg-surface, bg-card, bg-popover, bg-foreground/5, text-foreground, text-muted-foreground, border-border.
5. One solid button per view. tone="brand" at most once per screen, for its main action.
6. If DESIGN.md does not cover something, copy the closest existing pattern and say which one in your summary.
7. Before you finish: run `npm run ui:inventory` and fix every finding.

If you use Wingo UI, npx wingo-ui@latest agents install claude writes .claude/skills/wingo-ui/SKILL.md with the library's version of these rules, plus .mcp.json and a marked block in CLAUDE.md, and the MCP server's get_design_rules and get_theme_tokens tools return the current rules and CSS variables. The coding agents docs list every file it writes.

Step 5: How do you check that Claude Code followed the design system?

Two checks: one per file, right after each edit, and one across the app, before you call the work done. Consistent UI with AI agents depends on this step, because a rule the agent skips comes back to it as an error in the same session instead of in your review a week later.

Check every edited file with a hook

A PostToolUse hook runs after each Edit or Write. It receives the tool call as JSON on stdin, with the path in tool_input.file_path. The edit has already happened, so exit code 2 cannot block it, but the hooks reference (opens in a new tab) says Claude Code then shows the hook's stderr to Claude, which fixes the file. Exit code 0 hides stderr from Claude. Register it in .claude/settings.json (the args array spawns the script directly, without a shell, which the docs recommend for paths with ${CLAUDE_PROJECT_DIR}):

json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-ui.sh",
"args": []
}
]
}
]
}
}

The script greps the edited file for values outside the system. The pattern is adapted from the drift check at the end of our own DESIGN.md, plus hex and palette colors:

sh
#!/usr/bin/env bash
# .claude/hooks/check-ui.sh: flags values outside docs/DESIGN.md in the file Claude just edited
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in *.tsx) ;; *) exit 0 ;; esac
# library source keeps hex colors as overridable prop defaults
case "$file" in */components/ui/*|*/components/blocks/*) exit 0 ;; esac
colors='#[0-9a-fA-F]{6}|-(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}'
system='rounded-(sm|md|lg|xl)([^-]|$)|bg-(accent|secondary)([^-]|$)|z-[0-9]|text-(destructive|brand|success|warning|info)([^-]|$)'
if hits=$(grep -nE "$colors|$system" "$file"); then
echo "Design system drift in $file:" >&2
echo "$hits" >&2
echo "Use the tokens, sizes and radius tables in docs/DESIGN.md, or a component prop." >&2
exit 2
fi
exit 0

Make it executable with chmod +x .claude/hooks/check-ui.sh; it needs jq. Each pattern maps to a rule: palette classes bypass the tokens, rounded-lg and friends are off the radius table, z-50 skips the named layers, and text-destructive uses a fill color as text, which falls below WCAG AA on a dark card.

Inventory every route

The grep cannot see a page with four solid buttons, or a screen where every action is orange. The rendered DOM can. Our Button writes its decisions into attributes (data-slot="button", data-variant, data-tone, data-size), so a short Playwright script can count them on every route:

ts
// scripts/ui-inventory.ts: npx tsx scripts/ui-inventory.ts (with the dev server running)
import { chromium } from "playwright";
const BASE = process.env.BASE_URL ?? "http://localhost:3000";
const ROUTES = ["/", "/dashboard", "/settings", "/billing", "/onboarding"];
async function main() {
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
const findings: string[] = [];
for (const route of ROUTES) {
await page.goto(BASE + route, { waitUntil: "networkidle" });
const buttons = await page.$$eval('[data-slot="button"]', (els) =>
els
.filter((el) => el.getClientRects().length > 0)
.map((el) => ({
variant: el.getAttribute("data-variant") ?? "",
tone: el.getAttribute("data-tone") ?? "",
size: el.getAttribute("data-size") ?? "",
})),
);
const solid = buttons.filter((b) => b.variant === "solid").length;
const brand = buttons.filter((b) => b.tone === "brand").length;
const custom = buttons.filter((b) => b.tone === "custom").length;
const sizes = [...new Set(buttons.map((b) => b.size))].join(", ");
console.log(`${route}: ${buttons.length} buttons, ${solid} solid, ${brand} brand, sizes ${sizes || "none"}`);
if (solid > 1) findings.push(`${route}: ${solid} solid buttons, the rule is one per view`);
if (brand > 1) findings.push(`${route}: ${brand} brand buttons, brand is for the one main action`);
if (custom > 0) findings.push(`${route}: ${custom} buttons with a custom color prop`);
}
await browser.close();
if (findings.length > 0) {
console.error(findings.join("\n"));
process.exit(1);
}
}
main();

Install it with npm i -D playwright tsx and npx playwright install chromium, and add "ui:inventory": "tsx scripts/ui-inventory.ts" to package.json so the skill can name it. The one-solid rule is per view, so a page with an open dialog can legitimately fail it; treat that line as a prompt to look, and narrow ROUTES or the selector when a page earns an exception. Every Wingo UI component carries a data-slot. Select, Badge, Toggle and Tabs also write data-size and data-variant, so the script extends to them by changing the selector. Check the component file before you count a new one: Input, for example, does not write data-size.

Then look at the screens yourself. Screenshot each route at 390px and 1440px wide, in light and dark mode, and put them side by side. A table of numbers will not tell you that two empty states use different icons.

Where should you start?

Start with the size table. Write it into docs/DESIGN.md, point a project skill at it, and add the hook. From the next screen on, Claude Code gets an error for every off-table radius, raw z-index or palette color it writes. Then move the defaults you keep repeating into the provider, and run the inventory before each merge.

The Button, the UIProvider and Tones used here are free:

sh
npx wingo-ui@latest add button ui-config tones

For the server side of this setup, where the agent looks up real props instead of guessing them, read the component library MCP guide and the MCP docs.

Components in this post

  • Button

    The React button component every screen starts with: five variants, six tones, all sizes, and a loading state that never jumps.

    Free
  • UIProvider

    A React config provider for Wingo UI: app-wide default props for every component, the Intl locale and a reduced-motion policy.

    Free
  • Tones

    One way to color a React component: a tone or any CSS color becomes five CSS variables every variant paints with.

    Free

FAQ

How do I make Claude Code follow my design system?

Put the values where Claude Code cannot miss them: color tokens in your CSS, exact tables in a DESIGN.md that a project skill points to, and app-wide defaults in code through a provider. Then add a PostToolUse hook that checks each edited file, so drift is reported back to Claude in the same session.

Should I use the Claude Code frontend design skill for product screens?

Use it for new pages that need their own look. Its instructions push for a distinctive palette and typography per brief, which is the opposite of what screen 37 of an existing product needs, so product work should follow your own DESIGN.md.

What should a DESIGN.md for Claude Code contain?

Exact tables an agent can copy: surfaces and text levels, the size table (height, padding, font, icon per size), radius per size, the type scale, spacing steps and the prop names your components use. Rules without numbers, like "keep spacing consistent", give the agent nothing to copy.

How do I check that AI-generated screens are consistent?

Run a grep for values outside the system (hex colors, palette classes, off-table radii, raw z-index) on every edited file, and a browser script that counts each route's buttons by variant, tone and size. Then look at screenshots at 390px and 1440px wide in both themes.

Is a CLAUDE.md enough on its own?

No. Anthropic's memory docs say Claude treats CLAUDE.md as context and point to hooks for anything that must happen regardless of what Claude decides. Defaults in code and checks that run after each edit apply whatever the model chooses.

  • Claude Code
  • Design systems
  • Design tokens
  • Skills
  • Hooks

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Next.js and Tailwind CSS

Tailwind v4 Design Tokens: Colors, Dark Mode and Contrast

Tailwind v4 design tokens from a production React library: @theme inline, semantic surfaces, status colors, dark mode and a contrast check you can run in CI.

SRSerban Rusu·Oct 9, 2026·20 min read
Building UI with AI agents

AGENTS.md vs CLAUDE.md vs Cursor Rules for React

AGENTS.md vs CLAUDE.md vs Cursor rules for React and Tailwind: which agent reads which file in October 2026, and a rules file that keeps agents on your tokens.

SRSerban Rusu·Oct 9, 2026·14 min read
Next.js and Tailwind CSS

Next.js Component Library Setup for the App Router

Next.js component library setup for the App Router, tested on Next.js 16.4: use client boundaries, tokens in globals.css, dark mode without a flash, updates.

SRSerban Rusu·Oct 13, 2026·8 min read
Newsletter

Get new posts by email

New guides, tutorials and comparisons from the Wingo UI blog, sent when they are published.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • Theme generator

Resources

  • Docs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI