Claude Code Design System: Make 50 Screens Match

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:
- Tokens in CSS. Colors, elevation and layers as CSS variables, mapped to Tailwind classes, so the agent writes
bg-surfaceand both themes work. - A DESIGN.md with tables. Sizes, radius per size, type scale and spacing as numbers, plus the prop names every component uses.
- 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. - Defaults in code. App-wide choices set once in a provider, so the shortest JSX the agent can write is also the correct JSX.
- 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:
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:
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:
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:
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.
$ npx wingo-ui@latest add tonesApp-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:
After that, <Button>Save changes</Button> on any of the 50 screens is pill-shaped, and the agent never had to remember it.
$ npx wingo-ui@latest add ui-configStep 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.
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}):
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:
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:
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:
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.
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


