If you build UI with Cursor or Claude Code, you've probably had this happen. You ask for a settings page, it looks fine at first glance, and then you start noticing things. The save button is bg-blue-500, but your brand color is teal. One card has 18px of padding while every other card has 16. A grey shows up that isn't in any file you wrote.
None of this is the model being sloppy. It has no idea what your design rules are, so it falls back on whatever is most common in the code it learned from. You can correct it in chat, but that correction only lives as long as the conversation does.
What actually fixes it is boring: put your design values in code, give the agent a short rules file it reads every time, and check the output with two searches. Here's the whole setup.
The short version
-
Tokens in
tokens.cssor your Tailwind@theme. Every color, spacing step, radius, shadow and font gets a name. -
One rules file,
AGENTS.md, that tells the agent to use those names and nothing else. -
A one-line
CLAUDE.mdthat importsAGENTS.md, so Claude Code reads the same rules Cursor does. - Two grep commands that find any value the agent made up anyway.
Step 1: Give every value a name
The rules file needs something to point at, so tokens come first. Plain CSS works fine:
:root {
--color-primary: #0f766e;
--color-primary-hover: #115e59;
--color-text: #0f172a;
--color-text-muted: #475569;
--color-surface: #ffffff;
--color-focus-ring: #14b8a6;
--space-2: 8px;
--space-4: 16px;
--space-6: 24px;
--radius-md: 10px;
}
[data-theme="dark"] {
--color-text: #f1f5f9;
--color-surface: #0f172a;
}
Two small things here save a lot of cleanup later. Hover and focus get their own tokens, because if they don't, the agent invents a "slightly darker" shade every time it needs one. And dark mode changes the values behind the names, not the names themselves, so components never need a dark-mode color of their own.
The Tailwind v4 part people miss
If you use Tailwind, adding your colors isn't enough. The default palette is still sitting there, and blue-500 is a class the model has seen more often than any of your tokens. Clear the defaults first:
@import "tailwindcss";
@theme {
--color-*: initial;
--font-*: initial;
--radius-*: initial;
--shadow-*: initial;
--color-primary: #0f766e;
--color-primary-hover: #115e59;
--color-text: #0f172a;
--color-text-muted: #475569;
--font-sans: "Inter", system-ui, sans-serif;
--radius-md: 10px;
}
After this, bg-blue-500 just doesn't exist in your project. If the agent writes it, the element comes out unstyled and you spot it right away. On Tailwind v3 the equivalent is putting colors under theme.colors instead of theme.extend.colors, since extend keeps the full default palette around.
Spacing you can leave alone. Tailwind's spacing already runs on a 4px step, so you only need to say which steps are allowed. If you haven't settled on a scale, I wrote up a ready-made 8pt grid scale here.
Step 2: Write rules you could check
The rules file is what the agent reads before it touches any UI. The trick is to only write rules you could verify by reading the code. "Make it feel clean and modern" can't be checked. "No hex values outside tokens.css" can.
# Design system rules
All colors, spacing, radius, shadows and fonts come from
src/styles/tokens.css. Nothing else.
## Do
- Use var(--color-*), var(--space-*), var(--radius-*) or the
matching Tailwind class for every visual value.
- Spacing steps: 4, 8, 16, 24, 32, 48, 64px. Nothing in between.
- Buttons use --color-primary, and --color-primary-hover on hover.
- Every interactive element has a visible focus style
(--color-focus-ring).
## Don't
- No hex, rgb or hsl outside tokens.css.
- No arbitrary values like p-[18px] or bg-[#0f766e].
- No dark: color classes. Tokens handle dark mode.
## Missing a value?
Use the closest token and mention it in your reply.
Ask before adding a new one.
That last section matters more than it looks. Without it, the agent quietly invents a value whenever it can't find one. With it, it picks the nearest token and tells you, and you decide whether the system really needs something new.
Keep the file short, too. Anthropic's docs suggest keeping each CLAUDE.md under about 200 lines because longer files get followed less reliably, and Cursor's docs suggest under 500 lines per rule.
Step 3: One file for every tool
Each tool looks for instructions in its own place, which is where most setups get messy:
-
Cursor reads
AGENTS.mdin the project root, plus anything in.cursor/rules/(those files need the.mdcextension; plain.mdthere is ignored). The old single.cursorrulesfile still works but is the legacy format. -
Claude Code reads
CLAUDE.mdevery session, and it can import other files with@path. -
Copilot, Codex and a growing list of others read
AGENTS.mdas well.
So put the rules in AGENTS.md, and make your CLAUDE.md this:
@AGENTS.md
That's it. One file to edit, every tool reading the same rules.
If you'd rather not load design rules during backend work, both tools can scope a rule to certain files. In Cursor that's a .cursor/rules/design-system.mdc file with frontmatter:
---
description: Design system rules for UI code
globs: src/components/**/*.tsx, src/styles/**/*.css
alwaysApply: false
---
In Claude Code it's a file in .claude/rules/ with a paths list at the top:
---
paths:
- "src/components/**/*.tsx"
- "src/styles/**/*.css"
---
Step 4: Don't trust it, grep it
A rules file is a request. To know whether it worked, give the agent a prompt with plenty of design decisions in it ("a pricing section with three plans and a highlighted middle one" works well), then search the result.
Hard-coded colors outside your token file:
grep -rnE "#[0-9a-fA-F]{3,8}\b|rgba?\(|hsla?\(" src --include=*.tsx --include=*.jsx --include=*.css | grep -v tokens.css
Arbitrary Tailwind values:
grep -rnE -- "-\[(#|[0-9.]+(px|rem))" src
Empty output means the agent stayed inside the system. Anything else shows you exactly where it didn't, and usually there's a pattern, like the same missing grey turning up again and again. That tells you whether to tighten a rule or add a token. Once the searches pass a few times, put them in a pre-commit hook.
Grep won't catch everything, so still look at the page. Tab through it to check focus states, switch to dark mode, and watch for the sneaky case where every value is a token but the wrong one, like muted text on a main heading.
Where DESIGN.md fits
You'll see DESIGN.md mentioned a lot lately. It's an open format from Google Labs (still alpha): YAML tokens at the top, plain-language reasoning below. It's good for the why behind your system, like "primary is for one action per screen".
Two cautions. Not every tool picks it up automatically, so reference it from your rules file or add @DESIGN.md to CLAUDE.md. And don't keep the same hex values in DESIGN.md and your CSS. Two copies of a value will disagree eventually.
When tokens change
This is where a working setup usually breaks. If you rename --color-brand to --color-primary in CSS but not in the rules file, the agent keeps reaching for a token that's gone. Change both in the same commit, keep the old name as an alias (--color-brand: var(--color-primary);) until a search shows nothing uses it, and start a fresh chat after big changes.
Skipping the hand-written part
Writing all of this from scratch takes a while, which is why I built the export into Constrainly. Each builder exports your tokens as CSS or a Tailwind v4/v3 theme, plus a matching rules file saved as AGENTS.md, CLAUDE.md or a ready-made .cursor/rules file with the frontmatter already in place. The Design Language Studio does colors, type, spacing, radius and shadows in one pass and exports a single rules file for all of it. It's free and there's no account.
I've put a longer version of this guide, with more on Tailwind, hover and dark mode tokens, and keeping rules short, on my site: How to Make Cursor and Claude Code Follow Your Design System.
If you've found other rules that make a real difference with your agent, I'd like to hear them in the comments.


Top comments (0)