# UI Polish

> Upgrade a generic-looking UI into something a design-conscious team would ship. Enforces a real type scale, an 8-point spacing grid, semantic color tokens, full interaction states (hover/focus/disabled/loading/empty/error), and tasteful motion. Use when the user says "make this look good", "polish the UI", "improve the design", "this looks like AI slop", "make it look professional", or pastes a screenshot and says it looks off.

- Skill: `ak-ship/ui-polish` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ak-ship/ui-polish`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ak-ship/ui-polish/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ak-ship (https://skillmd.com/u/ak-ship)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ak-ship/ui-polish

---


# ui-polish — make the UI look like a real team shipped it

## When to use this skill

Trigger when the visual quality of an interface is the bottleneck. Strong signals:

- "make this look better", "polish this page", "improve the design", "this looks AI-generated"
- A screenshot in the conversation with the user pointing out spacing, alignment, or visual hierarchy issues
- A new component just landed and the user wants it to match the rest of the system

Do *not* trigger for: a11y-only requests (separate skill territory), pure animation work without a structural problem, or when the design system is locked and the user is asking you to *break* it.

## The output contract

Code changes that pass this checklist:

1. **Type scale**: at most 6 sizes in use, geometric ratio (e.g. 12, 14, 16, 20, 24, 32). No `font-size: 13px` one-off.
2. **Spacing**: every margin/padding/gap is a multiple of 4 (preferably 8). No `margin: 17px`.
3. **Color**: every color comes from a token (`color-bg-surface`, `color-text-muted`) — not a raw hex inline.
4. **States covered**: every interactive element has explicit `:hover`, `:focus-visible`, `:active`, and `:disabled`. Forms have loading + error + empty + success.
5. **Density**: deliberate. Marketing pages breathe. Dense data UIs are tight. Don't average.
6. **Motion**: at most one purposeful transition per interaction (e.g. 150ms opacity on hover). No bouncy nonsense.

## Workflow

### 1 — Diagnose before touching

Read the current component. Identify the actual problems, in priority order:

- **Hierarchy**: can the eye find the primary action in <1 second?
- **Alignment**: do edges line up? Is there a grid?
- **Spacing**: is there a rhythm, or are gaps random?
- **Contrast**: does text actually pass 4.5:1 against its background?
- **States**: what happens on hover, focus, disabled, loading, empty?

Name the top three problems out loud. Don't start editing until you can articulate what's wrong.

### 2 — Look at what already exists

- Is there a design system in the repo? (Tailwind config, shadcn/ui, Chakra, Radix, MUI?)
- What spacing scale is in `tailwind.config.{ts,js}` or the equivalent? Use it. Don't introduce new tokens unless the user asks.
- What components already exist? Reuse `<Button>` from the library before hand-rolling one.

### 3 — Apply the fixes in this order

1. **Structure first**: get the layout grid and visual hierarchy right. Use flex/grid; remove ad-hoc margins.
2. **Spacing**: replace every margin/padding with a token value. Use `gap` on flex/grid containers instead of margins on children.
3. **Type**: collapse every font size to the existing scale. Set deliberate weights — only `400`, `500`, `600`, `700` unless the design system uses something else.
4. **Color**: replace inline hex with tokens. If you need a new shade, add it to the config, don't sprinkle.
5. **States**: add `:hover`, `:focus-visible`, `:disabled`. Replace fade-in with a real loading skeleton or spinner. Add empty states with a one-line "why this is empty + the next action".
6. **Motion**: one short transition per interaction. `transition: opacity 150ms ease` is usually enough.

### 4 — Verify with the browser

If a dev server is running, use the preview tools (or Playwright via `browser-qa`) to:
- Screenshot the new state at desktop + mobile widths
- Tab through interactive elements and confirm focus rings are visible
- Hover over actionable items and confirm state change

Show the user the before/after screenshots. Don't just claim it looks better.

### 5 — Hand-off

Summarize what you changed in 5 bullets max:
- The structural fix (e.g. "switched to a 12-col grid")
- The spacing rule (e.g. "all margins now multiples of 8")
- The token usage (e.g. "replaced 6 inline hex with `color-text-*` tokens")
- The new states (e.g. "added loading skeleton, empty state, error inline")
- Anything that should be promoted to the design system

## Patterns and anti-patterns

✅ **Do**:
- Steal mercilessly from existing top-tier UIs. Linear, Vercel, Stripe, Notion — study their spacing rhythm before inventing your own.
- Default to a single accent color. Two is brave. Three is a brand crisis.
- Use `outline-offset: 2px` for focus rings; never set `outline: none` without a replacement.
- Match line-height to font size: body 1.5x, headings 1.2x.

❌ **Don't**:
- Don't add shadows to fix unclear hierarchy. Fix the hierarchy.
- Don't use `transition: all`. Be explicit about what animates.
- Don't reach for new fonts. The existing one almost always works with better weight + spacing choices.
- Don't center-align everything. Left-align body copy. Always.

## Example invocation

> User pastes a screenshot of a dashboard card: "this looks like AI slop, make it look good"

1. Diagnose: hierarchy is flat (label, value, delta are all the same weight), gaps are random (12, 17, 20px), the chart has no axis labels, focus state is missing on the time-range toggle.
2. Check repo — uses Tailwind with a custom scale. Spacing tokens: 1, 2, 3, 4, 6, 8, 12.
3. Restructure: bigger weight on the metric value (font-semibold, text-3xl), smaller muted label above (text-xs, text-muted-foreground, uppercase tracking-wide).
4. Normalize: all gaps to `gap-2` (8px) inside the card, `gap-6` (24px) between cards.
5. Add chart axis labels with the muted color token; add `:focus-visible` ring to the time-range button.
6. Add a loading skeleton (one shimmer line at the right height) for when the metric is fetching.
7. Show the user before/after, list the 5 changes.

## See also

- `code-auditor` — for finding hard-coded magic numbers and inline styles at scale
- `browser-qa` — to verify the polished UI doesn't break the user flows
- `refactor-master` — when polishing reveals that the component needs to be split

