Skip to content

Why your design engineer keeps fixing the same mistakes

When agents ship the UI, the design engineer fixes the same small mistakes on every pull request. Checks that know your design system can take that queue and leave the decisions.

Settings.tsx

- color: "#3b82f6"

+ color: var(--action)

check: use the colour token

- <SaveButton />

+ <Button>Save</Button>

check: reuse the component

- gap: 16px

+ gap: var(--space-4)

check: spacing from the scale

3 checks failingAll checks passed

The polish queue

Most teams I talk to ship UI with an AI agent now. Claude, Codex, Cursor.

Then the work lands on the design engineer's desk, and the same list comes back every time. A raw hex where a token exists. A new button when the library already has one. A form without a disabled state. A spacing value that is not on the scale.

None of these are design decisions. They are the same mistakes, fixed by hand, on every pull request. The faster the agent ships, the longer this queue gets.

Engineers have had linters, type checks and end-to-end tests for a long time. Design has tools too: stylelint, visual regression tests, accessibility checks. What they don't know is your own system: which token means what, which component already exists, which states every form needs. That part still lands on the design engineer, by hand.

Four layers an agent reads

What an agent can read decides what it gets right. I think of it in four layers.

Codewhat is there
Design systemwhat to use
Ruleswhat is allowed
Decisionswhy, and what was rejectedreview

Code says what is there. The design system says what to use: tokens, components, the states each component has. Rules say what is allowed, and a check enforces them. Decisions say why, and what was rejected, so the agent does not bring back something the team already dropped.

The middle of this is plain DRY. A colour is defined once, as a primitive. A semantic token says what it means. A theme decides which primitive that meaning points to, in light and in dark. A component that asks for the meaning follows every theme. A component that asks for the primitive skips the middle layer, and no theme can reach it.

The decision layer is the one I trust least. It only grows, and an old decision stays in the context long after it stopped being true. It earns its place only with a regular review that moves stale entries out.

A check that names the fix

A rule nobody checks is a wish. The agent reads it, and on the next screen it writes the raw value anyway.

So the rule needs a check, and the check runs before every commit. On a project this summer I set it up as a pre-commit hook: a design linter, not only a code linter. No hex values. No hard-coded font sizes or radii. No new component when one already exists: DRY, the same as in code. If something broke a rule, the commit stopped.

The part that mattered most was the message. An error that only says no sends the agent back to guessing. Mine named the closest token, something like: #f59e0b is a raw value, closest token: text-warning. The agent proposed the swap and asked. I decided.

When no token fit, the check stopped anyway, and the question came to me: is this a new token, or a mistake? That question is design work. Finding the raw value was not.

The gap between two checks

Two checks catch more than one. They still leave a gap.

One check reads the code: does a component ask for a primitive or a raw value? The other measures the result: in every theme, is the text readable on its background? Together they catch most of it.

On one project a change passed both. A component moved from a primitive token to a semantic one, exactly what the rules asked for. But a rule in the theme file found that element by its old class name, to give it white text on a strong background. After the change the rule no longer matched, and the label went dark on it. The code check was happy, because the component was fixed. The contrast check was happy, because the tokens were fine.

I found it by reading the file. Then it became a third check: a theme rule may not point at a primitive either. A bug that slips through marks a missing rule. The work is to notice where it lived.

Exceptions belong to the system

A rule is only as strong as the way it handles its exceptions. Take “no raw values”. It is a good rule, and it is not true everywhere.

A brand logo keeps its own colour. A third-party library needs an override where no token exists. These are fine, as long as each one is written down, with the reason, next to the line. The check skips it, and anyone who reads the line sees why.

color: #c2410c// brand logo, keeps its colour
.tooltip { background: #222 }// library override, no token
border-color: #e5e5e5no reason written
border-color: #e5e5e5copied by the agent

The dangerous exception is the one nobody wrote down. It looks exactly like a mistake, and an agent cannot tell the difference. Worse, it copies it. One raw value in the codebase is an example, and agents follow examples.

So an exception is part of the system, not a hole in it. It has a place and a reason. When the reason goes, the exception goes too.

What is left for the design engineer

When the checks take the repeated fixes, the review gets shorter, and what is left is a different kind of work.

Is this the right flow for the task? Does the screen say what it should? What should the empty state say before there is any data?

No rule writes these answers. These are the decisions a design engineer is there for. Without the checks they still get made, but later, after one or more rounds of fixes, and every feature ships slower.

That is the shift: less polishing, more deciding.

What changed in my work

The biggest change was where my fixes go. A fix used to go into the screen, and the same fix came back next week. Now it goes into the system once.

On one project the client wanted to see long names breaking the layout, so I added a switch for it. What broke became a rule, and every page I built after that already followed it.

That is how the same mistakes stop: they get fixed where the next screen comes from.

Who this is for

For design engineers, frontend leads and founders whose teams ship UI with AI agents.

FAQ

Do we need a designer if agents write the UI?

Yes. Agents write UI fast, and rules catch the repeated mistakes. Someone still decides the flow, what a screen says and what an empty state should say. With the checks in place, that person spends the time on those decisions instead of on fixes.

How do we make AI agents follow our design system?

Write down what an agent cannot guess: which tokens and components to use, which states every component needs, and which exceptions are allowed. Then check it before every commit. A rule nobody checks gets ignored.

What is a design linter?

A check for design rules in code, the way a code linter checks code: no raw colour where a token exists, no new component when one already exists. A useful one names the right token in its error, so the fix is one step.

Why do raw values break later?

A raw value ignores the theme. It looks right today, and when a theme or a token changes, it stays as it was. Nothing tells you it is now wrong.

Written byPortrait of Feri HorvathFeri