How to Get Pixel-Perfect UI From Claude Code (Stop Describing Buttons in Words)

AI coding tools don't have a taste problem. They have a spec problem. Ask Claude Code for “a clean, modern button” and you get a coin flip. Give it exact values (radius, padding, per-state colors, transition timing) and the output matches your design almost deterministically. In my blind test, a spec-grade prompt reproduced a button with all 11 measured CSS properties identical.

I ship UI with Claude Code and Cursor daily, and for months my least favorite loop was the visual one: generate a component, squint at it, then spend twenty minutes typing follow-ups like “a bit rounder” and “the hover feels too dark.” The logic was right on the first try; the pixels never were. So I stopped describing components in adjectives and started writing specs. Here's the exact method, and the experiment that convinced me it works.

Why do AI tools get UI “almost right”?

Because adjectives are lossy. When you write “modern button,” the model has to guess a radius, a padding pair, a font weight, a shadow, and four state colors. That's easily eight independent guesses. Even if each guess is reasonable, the odds that all eight match the picture in your head are close to zero.

The failure mode isn't ugliness. It's plausible wrongness: a button that would pass in someone else's app but clashes with yours. And because it looks 90% right, you fall into the correction loop: one vague adjustment per message, each one gambling the values you already liked.

What does a spec-grade UI prompt look like?

A spec-grade prompt has three parts: a strict preamble, exact values, and state diffs. The preamble tells the model not to improvise. The values leave nothing to guess. The state section describes hover, focus, and disabled as differences from default, which is both shorter and less ambiguous than re-describing the whole component.

Here's a trimmed version of the prompt format I now use:

Implement this component exactly. Every value is intentional.

Do not substitute your own design taste. Do not round values.



## Component: Button

### Default state

- Background: #2563EB

- Text color: #FAFAFA

- Corner radius: 12px

- Padding: 12px vertical, 20px horizontal

- Font: 15px / 600 (line-height: 1.25)

- Box shadow: 0px 2px 8px 0px rgba(37, 99, 235, 0.35)

- Transition: background-color 200ms ease-out, box-shadow 200ms ease-out



### Hover state (differences from default only)

- Background: #1D4ED8



### Disabled state (differences from default only)

- Background: #93C5FD

Two details matter more than they look. First, the “differences only” framing for states, since models handle diffs much more reliably than full restatements. Second, the line about not rounding values: without it, I've watched models quietly turn 12px into rounded-xl because it's the nearest Tailwind default.

Does this actually work? I ran a blind test

I designed a button (blue fill, 12px radius, custom shadow, hover and disabled overrides) and generated a spec prompt for it. Then I gave only the prompt to a fresh AI session that had never seen the design, and asked it to implement the component. No screenshots, no follow-ups, one shot.

When I rendered both side by side and compared computed styles in DevTools, all 11 properties I measured (background, text color, radius, padding, font size, font weight, box shadow, and all three transition values) were identical. The only drift was sub-pixel: a 0.75px height difference, because my spec hadn't pinned line-height. I added line-height to the spec format and that gap closed too.

That result changed how I think about the correction loop. The twenty minutes I used to spend nudging pixels wasn't an AI-capability tax. It was the cost of my own vague first prompt.

How do you add the rules AI always forgets?

Exact values fix the look; they don't fix the details that make UI feel professional. In my testing, models consistently skip four things unless told: visible :focus-visible rings, prefers-reduced-motion guards, minimum 44px hit targets on small controls, and proper disabled-state cursor handling.

So my prompts end with a short rules block, and I only include rules relevant to the component; a static badge doesn't need motion guards:

## Implementation rules

- Style keyboard focus with :focus-visible (never bare :focus):

  2px outline, offset 2px. Never remove focus outlines.

- Wrap transitions in @media (prefers-reduced-motion: no-preference).

- Transition only the listed properties — never `transition: all`.

- Interactive hit target must be at least 44×44px; extend with

  padding or a pseudo-element without changing the visual size.

This block does double duty. It raises the accessibility floor of everything the model produces, and it removes another round of “oh, I also need focus states” follow-ups.

Can you automate writing these prompts?

You can write spec prompts by hand; the format above is yours to steal, and for one-off components that's honestly enough. The friction shows up when you iterate: every design tweak means re-editing prose, and it's easy for the prompt to drift out of sync with what you actually want.

Full disclosure: this bugged me enough that I built a tool for it. UIPrompt is a small visual editor: you design the component with sliders and color pickers (radius, padding, states, transitions), and it generates the spec prompt live as you tweak. The blind test above is literally its output format; the relevant rules get attached automatically based on what your component uses. There's a free playground you can try without signing up, so I'll leave the judgment to you.

Tool or no tool, the principle stands on its own: describe UI in values, not vibes, and describe states as diffs. That single habit removed most of my visual correction loop with Claude Code.

Frequently asked questions

Does this work with Cursor and v0 too, or just Claude Code?

The format works across tools. In my testing the exact-values approach transfers directly, since the spec is plain language plus CSS values, which every serious coding model reads well. I keep a one-line preamble per tool (v0 gets “render a demo of every state”), but the spec body stays identical.

Why not just paste a screenshot instead of writing a spec?

Screenshots communicate layout well but values poorly. A model can't reliably read “12px radius, 200ms ease-out” off an image, and it can't see hover or focus states at all. My best results come from combining both: an image for context, a spec for the values that must be exact.

Isn't a long spec prompt slower than a short vague one?

Only on the first message. The vague prompt is fast to type and slow to converge; each correction round costs another generation and another review. The spec prompt front-loads the effort and usually lands in one shot. Over a real work session, it's the faster of the two by a wide margin.

What if I don't know the exact values I want?

Then borrow them. Inspect a component you like with DevTools and copy its computed values into the spec format, or start from a design system's documented tokens. The point isn't that you must invent numbers; it's that whatever taste you settle on should reach the model as numbers, not adjectives.


I'm Andy Liu, a frontend engineer who uses AI coding assistants daily and writes up what actually works. Questions or war stories about AI-generated UI? Get in touch.

留言