The bug is closed, the happy path runs, the data shows up. You ship. And that's the exact moment you skip the pass that separates a product people trust from a demo people tolerate. You skip it because polish feels like a bottomless pit — a vague, infinite, never-quite-done feeling that good designers must carry around like a curse. So you tell yourself it works, and works is the bar, and you move on.
Works is not the bar. Works is the floor. The gap between "works" and "finished" is not a feeling and it is not infinite. It is a short, boring, repeatable list of the same five things, wrong in the same five ways, every single time. You don't need taste to close that gap. You need a checklist and thirty minutes.
Here is why polish feels endless: you've never written it down. You sit on a screen, sense that something is off, can't name it, nudge a few pixels, lose the thread, and conclude that "having an eye" is a gift you weren't born with. That's not a skill problem. That's a process problem. You're running an unbounded search with no list of what you're searching for.
Designers who ship clean work are not seeing more than you. They're checking a finite set of things in a fixed order. The "eye" is mostly muscle memory for a routine. Make the routine explicit and you get most of the eye for free — today, on the screen in front of you.
Polish is not a vague feeling — it's a repeatable audit you run before every ship: alignment, spacing, consistency, states, and copy. Run the same five passes in the same order every time, and "finished" stops being mysterious. It becomes a checkbox you either ticked or didn't.
The power of a checklist is that it converts judgment into recall. You no longer ask "is this good?" — an impossible question with no edges. You ask "does everything line up? are the gaps consistent? is every value from the system? are all four states present? is the copy human?" Five questions, each with a yes-or-no answer, each fixable in minutes.
Works is not the bar. Works is the floor.
Run it as a single block, screen by screen, with the page in front of you and the code open in the other window. Each pass below is one sweep with one question. Don't try to do all five at once — you'll do all five badly. One lens, top to bottom, then the next lens.
Open the screen and let your eye fall down the left edge. In finished work, things share edges. Labels, inputs, headings, body text, and buttons stack against the same invisible vertical line, or against a small number of lines that form columns. In unfinished work, elements drift — a heading indented three pixels past the paragraph below it, an icon that sits half a pixel off its label, a card whose content hugs a different margin than the card beside it.
These drifts are individually invisible and collectively damning. Nobody points at a 3px misalignment and says "that's why this looks cheap." They just feel that it looks cheap. Alignment is the cheapest polish you can buy because the fix is usually deleting a one-off margin, not adding anything.
What to hunt for in this pass:
padding-left, a margin on one item but not its siblings).The usual culprit is per-element overrides fighting a layout that would already line up if you let it. The fix is almost always subtraction:
/* Before: hand-nudged, drifts by a pixel or two */
.card-title { margin-left: 3px; }
.card-icon { position: relative; top: 1px; }
/* After: let flex do the aligning, delete the nudges */
.card-header {
display: flex;
align-items: center;
gap: 8px;
}
Turn on your browser's layout overlay — the grid/flex inspector, or a one-line outline rule — and the drifts light up immediately.
/* Temporary X-ray. Delete before you ship. */
* { outline: 1px solid rgba(255, 0, 0, 0.2); }
If you only ever run one pass, run this one. This is the same instinct as the Squint Test from earlier — blur your focus, look at the structure, and the things that don't line up are the things that draw the wrong attention. Alignment is hierarchy's quiet partner: when edges agree, the eye relaxes and can finally see what you meant to emphasize.
In finished work, nothing floats — every element lines up to a column, an edge, or a baseline shared with something else.
Now look at the air. Alignment is about edges agreeing on the same line; spacing is about the gaps between things being the same size when they play the same role. Unfinished UIs have spacing that wanders — 14px here, 13px there, 20px in one card and 24px in its twin — because each gap was typed by hand in a different mood.
The reader who's been through this book already has the tool for this: The 8-Point Grid. Every gap, every padding, every margin is a multiple of 8 (or 4 when you need a finer step). Not 13, not 15, not 22. The point isn't that 8-multiples are magic numbers; it's that a small fixed set of values, used consistently, reads as deliberate, and deliberate reads as finished.
In this pass you're auditing two things: are the values on the grid, and are equal-role gaps equal?
The discipline that makes this fast is using your spacing scale as named steps instead of raw numbers. If you're in Tailwind, you're already most of the way there — p-4 is 16px, gap-6 is 24px — so the audit becomes "is there a raw style or an off-scale arbitrary value like gap-[13px] anywhere?" Grep for it.
# Find off-grid arbitrary values and inline styles to review
rg "gap-\[|p-\[|m-\[|style=\{\{" src/
If you're in plain CSS, lift the values into variables so the gaps can only ever be on-grid:
:root {
--space-1: 4px;
--space-2: 8px;
--space-3: 16px;
--space-4: 24px;
--space-5: 32px;
}
.stack > * + * { margin-top: var(--space-3); }
One specific gap to check on every screen: the rhythm between a label and its field versus between one field group and the next. A classic tell of unfinished forms is that the label sits the same distance from its own input as from the input above it — so the eye can't tell which label belongs to which field. The relationship is proximity: a label should hug its field (say 8px) and sit farther from the previous group (say 24px). Same idea, two different gap sizes doing real work.
When you find two cards with 20px and 24px padding, don't split the difference to 22px. Pick the grid value — 24px — and apply it to both. Averaging produces a third off-grid number and leaves you with three problems instead of one.
Third sweep. This time you're not looking at any one element — you're looking for elements that should be twins but aren't. Two buttons with subtly different corner radii. Three shades of "gray text" that are actually three different hex values. Body copy that's 15px in one card and 16px in the next. Icons pulled from two different sets, one hairline-thin and one chunky, sitting side by side.
Each of these is a small betrayal of the same promise: that this product was built by one mind with one set of rules. Inconsistency is the loudest amateur signal there is, because the brain is a difference-detector — it can't help but notice that these two things that look almost the same are not the same, and it reads the mismatch as carelessness.
This is exactly what Decide Once, Reuse Forever and Tokens as Constants were for. If your radii, colors, type sizes, and shadows all come from named tokens, this pass is short, because divergence is hard to introduce by accident. If they don't, this pass is where you find the cost.
The five things to reconcile, in order:
font-weight: 500 where everything else uses 600.The fastest way to catch color drift is to grep for raw hex codes and count the survivors:
# How many distinct grays did you actually invent?
rg -o "#[0-9a-fA-F]{6}" src/ | sort | uniq -c | sort -rn
If that list is forty colors long for what should be a six-color palette, you've found your afternoon. Replace the near-duplicates with the token and watch the screen settle.
The brain is a difference-detector; it reads two almost-identical things as carelessness, not nuance.
You don't have to fix all of it in one sitting. But you do have to fix the ones that sit next to each other, because adjacency is what makes inconsistency visible. Two slightly different button styles on the same screen is glaring; the same two styles three screens apart is forgivable. Prioritize co-located offenders.
The last two lenses are where most "it works" products quietly fall apart, because the demo only ever shows the happy path with good data and a human label nobody re-read.
First, states. Run the Four States Rule across the screen: for every place that shows data, does it have an empty state, a loading state, an error state, and the ideal populated state you've been staring at this whole time? The unfinished version shows a blank white void when the list is empty, a layout-shifting pop when data arrives, and a raw red error string — or worse, nothing — when the request fails.
You don't need elaborate illustrations. You need each state to exist and to tell the truth:
function InvoiceList({ status, invoices }: Props) {
if (status === "loading") return <ListSkeleton rows={5} />;
if (status === "error") return <ErrorState onRetry={refetch} />;
if (invoices.length === 0) return <EmptyState cta="Create your first invoice" />;
return <Table rows={invoices} />;
}
A loading skeleton that matches the real layout, an empty state that points at the next action, an error state with a retry — that's the whole job. It's also where the Built for the Edges mindset pays off: the empty and error states are edge cases on paper, but they're the first thing a brand-new user sees, so they're not edges at all. They're the front door.
Every new user's first encounter with your app is the empty state — no data yet, nothing to show. If that screen is a blank void, you've made your strongest impression a shrug. Design the empty state as a welcome and a nudge toward the first win.
Second, copy. Read every visible string out loud as if a person wrote it for another person. Buttons especially. Following Label the Outcome, a button should name the result, not the mechanism: Create invoice, not Submit; Save changes, not OK; Delete project, not a bare Delete with no object. Check your error messages too — "Something went wrong" is a placeholder, not a message. Tell them what happened and what to do next.
Quick copy checklist for the pass:
Submit, OK, or Done doing the work of a real verb-plus-object.TODO, no [placeholder], no debug strings.Now, the actual artifact. Here is the checklist itself, the thing you reuse on every screen before you ship. Paste it into your repo, your PR template, wherever you'll see it:
# The Polish Checklist — run per screen, in order
# 1. ALIGNMENT — does everything line up to an edge/column/baseline?
# — any 1px drifts? any stray margins to delete?
# 2. SPACING — every gap a multiple of 8 (or 4)?
# — equal-role gaps equal? label hugs field, groups breathe?
# 3. CONSISTENCY— type, color, radius, icons, shadows all from the system?
# — fix co-located offenders first.
# 4. STATES — empty, loading, error, AND ideal all present and honest?
# 5. COPY — buttons name outcomes? no Submit/lorem/TODO? errors useful?
# FINAL: squint at the screen. One loudest thing? Edges quiet? Ship.
The happy path with good data is the easiest 80% of a screen and the least convincing — finished work proves itself in the empty, the loading, and the broken.
That's the whole pass. Five lenses, one sweep each, on every screen that matters. The first time it'll take you an hour because you'll find a backlog. After that it's thirty minutes, then fifteen, because each screen you fix raises the baseline of every screen you build next.
Open your most-used screen — the dashboard, the main list, the settings page — with the page in one window and the code in the other. Run alignment, spacing, consistency, states, copy, in that order. Write down what you find before you fix anything; you'll be surprised how long the list is.
Drop a temporary * { outline: 1px solid rgba(255,0,0,0.2); } rule in, find every drift, and delete the stray margins and one-off paddings causing them. Success looks like a clean left edge and shared baselines. Remove the outline rule before committing.
Grep for off-scale values (rg "gap-\[|p-\[|m-\[|style=\{\{" src/) and snap each one to the nearest 8 (or 4). Make equal-role gaps equal — labels hugging fields, groups breathing apart. No averaging to a third off-grid number.
Run rg -o "#[0-9a-fA-F]{6}" src/ | sort | uniq -c | sort -rn, find the near-duplicate grays, and collapse them to one token. Or unify your button radii, or your icon stroke weights. Fix the offenders that sit next to each other first.
For every data view on the screen, confirm an empty, loading, and error state exists alongside the ideal one. Add a skeleton that matches the layout, an empty state that points at the next action, and an error state with a retry. Trigger each one manually to prove it renders.
Rename buttons to name the outcome, kill every Submit/OK/lorem/TODO, and rewrite useless error messages. Then blur your eyes one last time: is there one loudest thing, do the edges stay quiet, does it look like one mind built it? If yes, ship it.
Finished isn't a feeling you wait for — it's a checklist you finish.