# UI Review

> After coding or changing frontend UI and capturing screenshots for the user to review (a new screen/component, or a before/after pair), launch a FRESH, independent reviewer subagent that has NONE of the coding context and critiques the screenshots like a senior UI/UX designer — catching broken layout, overlap, clipped/hidden content, and bad spacing (e.g. a CTA floating with a big empty gap, or an element flush against a system bar). Use every time you capture UI to show the user. Works for any project, web or mobile.

- Skill: `tiennguyen1203/ui-review` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add tiennguyen1203/ui-review`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tiennguyen1203/ui-review/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: tiennguyen1203 (https://skillmd.com/u/tiennguyen1203)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/tiennguyen1203/ui-review

---


# UI Review — Fresh Eyes, No Coding Bias

> **STOP — if a design reference exists, ONE review is not enough.** You must run TWO
> independent passes: **(a)** the blind UX pass (actual pixels only) below, **and (b)** the
> **Design-fidelity comparison** (reviewer gets expected+actual, lists every mismatch — see the
> MANDATORY section below). The blind reviewer never sees the design, so it will happily "PASS" a
> build that visibly diverges from it. **Never eyeball expected-vs-actual yourself and call it
> "matched"/"minor"/"PASS" — that is self-judging, the exact thing this skill exists to stop.** Do
> not report any verdict until BOTH passes have run, and treat every fidelity mismatch as a defect
> to fix unless it is an explicitly-agreed out-of-scope divergence.

When you have written or changed frontend UI and captured screenshots to show the
user (a new screen, or a before/after pair), you MUST run those screenshots past an
**independent reviewer** before presenting them as done or "PASS".

**Run it on the screenshots the moment they exist — before assembling any evidence
report.** The reviewer needs only the pixels + the rubric; it never consumes the report,
so there is no reason to package the report first. When you are also producing a
visual-evidence report (the usual case), the order is: capture → **review here** → (fix +
re-capture + re-review on any blocker/major) → *then* assemble & publish the report on the
settled pixels, and deliver the link with the verdict. Reviewing first means a blocker is
caught before you spend effort packaging a report you would only have to rebuild. Running
before vs after the report changes nothing about the reviewer's independence — it sees only
the screenshots either way — so put it first purely to avoid the rebuild.

**Why:** the agent that wrote the code cannot fairly judge its own UI. It knows what
it *intended*, so it rubber-stamps ("clears the nav bar → PASS") and misses what a
real designer sees (a button floating with a huge bottom gap, text 2px from an edge,
inconsistent padding). The fix is a reviewer with **fresh eyes and zero knowledge of
the code or the intent.**

## Design-fidelity mode — MANDATORY when a design reference exists

If the screen has a **design reference** — a Claude Design project (via the DesignSync MCP), a
Figma frame, a mockup, any "this is how it should look" source — the blind UX pass
below is **not enough**. Fresh-eyes UX review cannot tell you whether the build matches the design;
it will pass things that are wrong and flag things that are right (it read the real product name
"SPECK-Pe" as clipped text, and a deliberate definition-list as a "river"). So you MUST **also**
run a **design-fidelity comparison**:

0. **Read the design CHANGELOG first, and reconcile by timestamp.** The Claude Design project keeps
   a `CHANGELOG.md` (newest-first, each entry timestamped) recording every change to a layer / screen /
   component / token / copy. On each round: pull `CHANGELOG.md`, read every entry **newer than the last
   timestamp you synced** (tracked repo-side, e.g. `docs/DESIGN-SYNC.md`), and treat each new entry as a
   reconcile task — apply it to the build, don't just eyeball the screens. Then advance the last-synced
   timestamp. This is what stops silent misses when the design moves between rounds (a column re-aligns,
   a note is rewritten). If the project has **no** CHANGELOG yet, ask the user to add one (see the design
   setup guide) and, until then, fall back to a fresh full pull + careful diff.

   **Scope of the CHANGELOG: the design-fidelity pass 1(b) + my own build reconcile ONLY — NEVER the
   blind pass 1(a).** The changelog tells the *builder* and the *fidelity comparison* what the design
   now is. It must **not** reach the blind reviewer (that would re-inject context and kill the
   independence). And it must **not** narrow the review to "only what changed": the blind pass judges
   **every screen of the current feature** — all the screens I built/touched — on their own merits,
   changed or not. A screen with no changelog entry this round is still reviewed in full.

1. **Get the design image for EVERY screen and state — from the REAL design, freshly pulled.**
   Render the design and screenshot it (Claude Design: serve the `.dc.html` + `support.js` locally
   and screenshot each screen/state; Figma: export the frame). One design image per built screen per
   state — same states, desktop + ~390px, light + dark. **Every screen you built gets its own
   expected image**; never pair some screens with an image and others with only a text spec.

   - **Re-pull the design FRESH at the start of every round.** The design evolves between rounds
     (a column re-aligns, a note is rewritten, a grid width changes, a name is renamed). A pull from
     earlier in the session is **stale** and silently causes mismatches — re-fetch before you build,
     before you compare, before you report. Diff against the fresh design, never against memory.
   - **The "expected" must be the design's OWN pixels — NEVER a hand-rebuilt lookalike.** Do not
     re-implement a screen's markup by hand and pass the reconstruction off as "expected": it drifts
     from the real design (wrong sample data, a stale layout, guessed tokens) and defeats the whole
     comparison — the reader is then comparing the build against *your* guess, not the design. Render
     the design's own files, or have Claude Design export the screen PNG.
   - If a screen's own file is genuinely too large to render whole (e.g. a truncated fetch), render
     its own **components/patterns from their smaller files** and/or get **Claude Design to export**
     the screen — that is a **blocker to surface**, not a licence to ship a hand-built approximation
     or a text-only spec. If you ever must show a reconstruction, label it plainly as one and still
     verify it against a fresh real render/export before trusting it.
2. **Give the reviewer BOTH images per state** — the **design (expected)** and the **build
   (actual)** — and tell it which is which. (This is the opposite of the blind pass: here the design
   IS the ground truth, and bias toward it is the point.)
2b. **Run the quantitative pixel-diff to LOCALISE where the pixels diverge — a signal, not a verdict.**
   Eyeballing two PNGs misses ~1px type deltas and near-identical wrong tokens; a mechanical diff
   points the reviewer straight at the divergent regions. Render/export the design "expected" and
   capture the build "actual" **at the same viewport width**, then run the bundled helper (prefix
   with your shell's nvm line if `node` isn't on PATH; first use only:
   `npm --prefix ~/.claude/skills/ui-review/scripts install`):

   ```
   node ~/.claude/skills/ui-review/scripts/compare-fidelity.mjs <expected.png> <actual.png> \
     --out <screen>-<state>-fidelity-diff.png
   ```

   It prints a JSON summary — `matchRate`, `diffPixels`, and size-mismatch `notes` — and writes a
   **magenta diff-highlight image**. Do two things with the output: **(i)** give the diff image +
   match-rate to the fidelity reviewer so it knows WHERE to look; **(ii)** embed the diff image in
   the evidence report, beside that state's expected/actual pair.

   **It is a SIGNAL that steers the eye, NEVER a verdict.** Anti-aliasing, font-rendering and
   sub-pixel shifts inflate the diff, so a correct build rarely hits 100% — do not treat a low
   match-rate as an automatic defect or a high one as an automatic pass. A pixel-cheap change can be
   semantically wrong (wrong copy at the same size, a near-identical wrong colour token) and still
   score high. It **complements, never replaces, the by-number checks**: the diff shows *where* type
   or spacing differs; you still read the design's `ty.*` / Typeset token and confirm the number.
   The independent reviewer still judges; every real mismatch it confirms is still a defect to fix.
3. **Have it compare against the design, element by element, TO THE PIXEL** (use the diff-highlight
   image to target the divergent regions), and flag every mismatch:
   - **Layout & structure** — column/grid layout, alignment across rows, section order, where each
     element sits. (E.g. "design lays the row out as 4 aligned columns: name+code · brand+stage ·
     roles · action; build uses a loose flex that doesn't align across rows.")
   - **Component variants** — button height/padding/border/radius, pill/chip style (filled vs
     outlined/dashed), input height, card border & **shadow**. Name the specific variant.
   - **Typography — pixel-exact against Claude Design.** Font **family**, **size (px)**, **weight**,
     line-height, letter-spacing, case, muted-vs-strong — matched to the design's actual values, not
     "close enough". A heading that should be 16px serif rendered as 13px sans is a defect; name the
     expected value vs the built value. Read the design's type tokens (`ty.*`, the Typeset variants)
     when in doubt and check the build resolves to the same one.
   - **Spacing** — padding and margins inside and between elements; horizontal insets on lists/bullets.
   - **Ordering** — item order (e.g. user-added items last), grouping.
   - **Missing / extra elements** — anything in the design absent from the build (a line, a count,
     an icon) or vice-versa. Content that can't exist yet (real data pending) may be **stubbed but
     must keep the design's structure** — flag if the structure is dropped, not just the value.
   - **Color** — semantic/token usage matching the design in both themes.
4. **Every mismatch is a defect to fix** unless it's an explicitly-agreed divergence the caller
   states up front (e.g. "app-shell nav is a later ticket"). Do not accept "close enough".

**No deferring, no downplaying — build each screen right as you go.** Every in-scope mismatch is
fixed to the design **now**, before the screen is called done — including ones the reviewer tags
**minor**. "A reasonable consequence of the scope cut", "close enough", "a later ticket", "minor, skip
it" are NOT valid outs for an in-scope screen; the only deferral allowed is a divergence the caller
agreed to **up front and in writing**. If you catch yourself narrating why a mismatch is acceptable
instead of fixing it, stop and fix it. Do not accumulate "known drift"; each build step lands
design-accurate or it is not done.

**How to fix a mismatch — build to the design, or ASK Claude Design; NEVER invent.** Claude Design
owns UI/UX; you only request/ask. So a fidelity fix is one of exactly two things: **(1)** the design
specifies the correct value (title, copy, colour, layout, control) and the build got it wrong → build
to the design verbatim; or **(2)** the design does **not** specify what the build needs — an undefined
behaviour, a control's placement, a navigation (how a row opens edit / reaches a destructive action),
a mobile/~390px layout — → **STOP and hand the user a ready-to-paste Claude Design prompt (questions
only), let Claude Design decide + update the screens, re-pull, then build to it.** Do **NOT** hand-code
your own answer — inventing where a button goes, what a row click does, a reworded string, or a phone
layout is itself a UI/UX decision you are not allowed to make. "Fixing drift" is never a licence to
invent; it is build-to-design or ask.

Run BOTH passes: the fidelity comparison (against the design) AND the blind UX pass (for problems
the design itself might have). Report both. The deliverable is only done when the build matches the
design for every state, minus the agreed divergences.

## The one rule that makes this work (blind UX pass)

Launch a **separate subagent** (via the Agent tool) whose prompt contains **ONLY**:
- the screenshot file path(s), and
- the review rubric below.

Do **NOT** put any of this in the reviewer's prompt:
- what the code change was, which files/props/values changed,
- what bug you were fixing or what you "expect" to see,
- your own verdict, or words like "should now be fixed / should clear the nav bar",
- the design, the design CHANGELOG, or "what changed this round".

Any hint of intent re-introduces the bias you are trying to remove. The reviewer must
form its judgement from the pixels alone. Give it the images and the rubric, nothing
else.

**Review the whole current feature, judged on its own merits.** Give the blind reviewer **every
screen you built or touched** for this feature (all states, desktop + ~390px, light + dark) — not
just the screens that changed this round, and never scoped by the changelog. Its job is a fair,
context-free UX critique of the feature as it stands now; a screen that had no design change this
round is still reviewed in full, because a fresh problem can surface anywhere. The changelog and the
fresh design pull serve the fidelity pass and my building — they never gate or narrow the blind pass.

For a **before/after** pair, you MAY tell it "these are two states of the same screen,
image A then image B" so it can compare — but still say nothing about what changed or
which one is supposed to be better. Ask it to judge both on their own merits and
whether B is genuinely better *without new problems*.

## How to run it

1. Save the screenshot(s) to files (PNG). For mobile, capture with
   `adb exec-out screencap -p > shot.png` (avoid `uiautomator dump` — it hangs heavy
   RN screens; see project notes). For web, use the browser/devtools screenshot tools.
2. Launch the reviewer with the Agent tool (a general-purpose / catch-all agent is
   fine). Run it in the background if you have other work; otherwise wait for it.
   Its prompt = the image path(s) + the rubric below + the output format.
3. Read back its findings. Treat CONFIRMED blocker/major issues as real: fix them,
   re-capture, and review again — do not present the UI as done while a major issue
   stands. Relay the reviewer's verdict to the user in your own summary; never bury it.

## Keep it cheap — scope the re-runs, never the first pass

The pipeline is expensive (each reviewer reads many PNGs, and re-capturing every frame + re-running
both subagents on every fix is the biggest repeated cost). Scope the **repeats** — but never the
first pass, and never at the cost of a guarantee.

- **First review of a feature = FULL.** Capture every state × desktop + ~390px × light + dark and run
  both passes on the full set. The blind pass always sees every screen of the feature. Dark is
  captured for every state at least once here, so a dark-only contrast/token bug gets one chance.
- **Re-review after a fix = only the AFFECTED frames.** When you fix findings and re-capture, re-run
  the reviewer on just the frames the change could plausibly move (the screens/states the edit
  touched), plus any frame whose diff was borderline. A 1px atom tweak does not need 30 frames
  re-captured or both full subagents re-run — capture the handful it affects and re-review those. Say
  in your summary which frames you re-checked and why the rest are unaffected. A **shared atom/token**
  change has a correspondingly wide affected set — scope honestly, don't under-claim to save tokens.
- **Diff-gate which frames reach the fidelity reviewer.** Run `compare-fidelity.mjs` (step 2b) on
  every expected/actual pair first, and hand the reviewer the pairs that actually diverge + their
  diff-highlight image, rather than all of them. A pair at ~100% match with a clean diff needs a
  glance, not a full element-by-element write-up — **but still confirm the by-number type values** on
  any pair you skip on the diff's strength, because a pixel-cheap change (wrong copy at the same size,
  a near-identical wrong token) scores high. The diff gates **effort, never coverage**: every state is
  still captured and still goes in the report.
- **Dark theme: always on the first pass; on re-runs skip dark only for a provably theme-neutral
  change** (pure layout / spacing / copy / a light-only token). Any change touching colour, a token,
  contrast, a border, a shadow, or a fill re-captures BOTH themes — that is exactly where dark-only
  defects live.

(**Figma projects:** the sibling **`check-ui`** skill is the Figma-based equivalent of the
design-fidelity pass — it diffs changed UI components against their Figma source. Use `ui-review` for
Claude Design / rendered-screenshot comparison; `check-ui` when the reference is Figma.)

## Reviewer prompt — paste this rubric into the subagent

> You are a senior UI/UX designer doing a critical design review. You are given only
> screenshot(s) of a screen — no code, no context, no description of intent. Judge
> what you SEE. Be skeptical and specific; assume there is probably something wrong and
> look hard. Do not rubber-stamp.
>
> Read each image and evaluate:
> 1. **Clipping / hidden content** — text truncated or cut off unexpectedly; content
>    hidden behind or overlapping the status bar, notch, home indicator, system
>    navigation bar, tab bar, or keyboard; anything running off the screen edge.
> 2. **Overlap** — elements overlapping that should not (button over text, badge over
>    label, footer over a field).
> 3. **Spacing — too much** — a CTA or footer floating with a large empty gap above the
>    bottom bar / tab bar; awkward voids; a lone element stranded in whitespace. This is
>    the most commonly missed defect — call it out precisely (roughly how large the gap
>    is relative to normal padding).
> 4. **Spacing — too little** — elements flush against a screen edge or a system bar
>    with no breathing room; cramped rows; touch targets that look too small or too
>    close together.
> 5. **Alignment & consistency** — misaligned or off-center elements; uneven margins
>    between similar items; inconsistent padding, font sizes, or component styling.
> 6. **Overflow** — text overflowing its container; unwanted horizontal scroll; wrapped
>    text that breaks the layout.
> 7. **Overall** — does this look like a shipped, designer-quality screen, or does
>    something feel off?
>
> For a before/after pair: evaluate each image, then say whether the second is a genuine
> improvement AND whether it introduced any new problem (e.g. fixed an overlap but opened
> a big gap).
>
> Output format — per screen:
> - **Verdict:** PASS (looks shipped-quality) or ISSUES.
> - If ISSUES, a list, each: **[blocker | major | minor]** — where on the screen, what
>   is wrong, and what "good" would look like. Be concrete ("the black ADD button sits
>   ~80px above the tab bar, leaving a dead gap; it should sit ~12–16px above it").
> - No praise padding. If it is genuinely clean, a one-line PASS is enough.

## Reporting back

Summarize the reviewer's verdict to the user honestly. If it found blocker/major
issues, say so plainly, fix them, and re-review — never present the reviewer's
"ISSUES" as if it were a pass. If the reviewer says PASS, you may present the UI as
verified by an independent review.

**Attribute every finding — the reviewer sees the whole screen, not just your diff.** A
fresh-eyes reviewer flags whatever is wrong in the pixels, including defects that were
already there before your change. For each finding, check it against your actual diff:
- **Introduced by this change** → it's yours to fix now (fix → re-capture → re-review).
- **Pre-existing / out-of-scope** (your diff didn't touch it) → surface it to the user as
  pre-existing, don't silently swallow it, and don't auto-"fix" it into scope creep —
  especially anything that might be deliberate (a design decision, an intentional duplicate)
  which must be checked against the design reference before being touched. Recommend a
  separate ticket.

So the honest close is not a blanket "PASS" or "ISSUES" — it's: *my change* passed (no
findings against what I touched), and the reviewer also surfaced N pre-existing issues on
this screen, here they are.

