--- name: reviewing-interface-quality description: Use when asked to review, critique, audit, or improve an existing user interface — "review this UI", "why does this look generic", "make this more polished", "design feedback", "does this look AI-generated", "audit this page". Also use as the final gate before shipping any frontend work, and when a UI renders correctly but feels unfinished, cheap, or inconsistent without an obvious cause. --- # Reviewing Interface Quality ## Overview "Make it look better" is not actionable. Interface quality problems have specific causes — an unenforced spacing scale, hierarchy varying on four dimensions at once, a state that was never designed — and a review is only useful if it names the cause and the fix. **Core principle:** Every finding names a location, a cause, and a concrete replacement value. "The spacing feels off" is not a finding. "`.card` uses `padding: 14px` while everything else uses the 8px scale — change to `var(--space-s)`" is. **Companion skills:** `designing-frontend-interfaces` (visual), `designing-user-experience` (behavior), `building-accessible-interfaces` (access). This skill audits against all three. ## The Iron Law ``` NO FINDING WITHOUT EVIDENCE YOU ACTUALLY GATHERED ``` Reviewing an interface means looking at it. Reading the source and inferring how it renders is not a review — it misses exactly the class of defect a review exists to catch: overflow, collapsed layouts, invisible text, layout shift, broken states. **Minimum evidence before writing a single finding:** a screenshot at 1440px, a screenshot at 390px, and the computed values for anything you are about to call inconsistent. ## Checklist 1. **Gather evidence** — render at two widths, capture DOM and computed styles 2. **Pass 1: First impression** — 5 seconds, before analysis 3. **Pass 2: System consistency** — the highest-yield pass 4. **Pass 3: Typography** 5. **Pass 4: Layout and responsive** 6. **Pass 5: States and behavior** 7. **Pass 6: Accessibility** 8. **Pass 7: Motion and performance** 9. **Report** — severity-graded, each with location, cause, fix ## Step 1: Gather Evidence ```python from playwright.sync_api import sync_playwright URL = "http://localhost:5173" # or file:///abs/path/index.html with sync_playwright() as p: b = p.chromium.launch() pg = b.new_page(viewport={"width": 1440, "height": 900}) errors = [] pg.on("console", lambda m: m.type == "error" and errors.append(m.text)) pg.goto(URL); pg.wait_for_load_state("networkidle") pg.screenshot(path="/tmp/rev-desktop.png", full_page=True) # Horizontal overflow — always a bug print("OVERFLOW:", pg.evaluate("""() => [...document.querySelectorAll('*')] .filter(e => e.getBoundingClientRect().right > document.documentElement.clientWidth + 1) .slice(0, 10).map(e => e.tagName + '.' + e.className)""")) # Every distinct value actually used — the consistency audit for prop in ["font-size", "font-family", "border-radius", "box-shadow", "padding", "color"]: vals = pg.evaluate("""(p) => [...new Set([...document.querySelectorAll('*')] .map(e => getComputedStyle(e)[p]))].filter(v => v && v!=='none' && v!=='0px')""", prop) print(f"{prop}: {len(vals)} distinct →", vals[:14]) pg.set_viewport_size({"width": 390, "height": 844}) pg.screenshot(path="/tmp/rev-mobile.png", full_page=True) print("CONSOLE ERRORS:", errors) b.close() ``` Then **read both screenshots**. The distinct-value counts tell you where the system broke; the screenshots tell you what it looks like. You need both. For a server-backed app, wrap this with `testing-webapps/scripts/with_server.py`. If the UI genuinely cannot be rendered (no runtime, a fragment, a component in isolation), say so explicitly in the report and mark every visual finding `PLAUSIBLE` rather than `CONFIRMED`. ## Step 2: First Impression Look at the desktop screenshot for five seconds and answer before analyzing anything: 1. **What is this?** If unclear in 5s, the hierarchy has failed. 2. **What should I do?** The primary action should be unmistakable. 3. **Where does my eye land first?** Is it the most important thing? 4. **Could I name the aesthetic direction?** "Clean" and "modern" mean there is no direction. 5. **Would I guess this was generated?** If yes, name the specific tells. Write these answers down before proceeding. Once you start auditing details you cannot recover the naive read, and it is the read every real user has. ## Step 3: System Consistency Highest yield. Most "looks unpolished" comes from here, and every finding is objective. | Check | Threshold | Common cause | |---|---|---| | Distinct `font-size` values | > 8 | Sizes chosen ad hoc, no ratio scale | | Distinct `font-family` values | > 3 | Third face crept in, or a fallback resolving differently | | Distinct `border-radius` values | > 2 | Mixed radii — the loudest amateur signal | | Distinct `box-shadow` values | > 3 | A new shadow invented per component | | Padding values off the space scale | any | Raw px instead of tokens | | Distinct grays in `color` | > 5 | No ink ramp; each component picked its own | | Accent color surface area | > 10% | Accent used as decoration rather than emphasis | ```bash # Fast static pass over source grep -rEo '(padding|margin|gap|font-size):[^;]+' src/ | grep -E '[0-9]+px' | sort | uniq -c | sort -rn grep -rEo 'border-radius:[^;]+' src/ | sort | uniq -c ``` **Also check:** are design tokens declared but not used? A `:root` block with a beautiful scale and components full of raw px is a very common and very fixable finding. ## Step 4: Typography - [ ] Sizes follow one ratio — not eyeballed - [ ] Display face is not Inter / Roboto / system-ui - [ ] Three weights maximum - [ ] Body measure 60-75ch — not full page width, not 30ch - [ ] Line-height varies inversely with size (body ~1.6, display ~1.0) - [ ] Large display type has negative tracking - [ ] Body text has no added letter-spacing - [ ] No centered paragraphs; no all-caps sentences - [ ] Numeric columns use `tabular-nums` - [ ] Headings use `text-wrap: balance` - [ ] No orphaned single words on heading final lines ## Step 5: Layout and Responsive - [ ] No horizontal page scroll at any width (check the overflow query output) - [ ] Nothing touches the viewport edge without intended padding - [ ] Related items are visibly closer than unrelated ones (4:1 inner/outer ratio) - [ ] Section spacing varies with importance — not uniform down the page - [ ] Alignment is consistent — no near-misses (12px vs. 16px left edges) - [ ] At 390px: nothing overlaps, no text clipped, no fixed element covering content - [ ] Tables and code blocks scroll inside their own container, not the page - [ ] Images have `width`/`height` or `aspect-ratio` — no layout shift - [ ] `100dvh`/`100svh` rather than `100vh` - [ ] The grid is broken at least once, deliberately — or the design is a uniform centered column ## Step 6: States and Behavior Check each against `designing-user-experience`. These are usually **missing**, not wrong — so audit the code, not just the render. - [ ] Empty (first use) distinct from empty (no results) distinct from empty (cleared) - [ ] Loading does not blank already-visible content - [ ] Skeletons match the real content's shape and are delayed ~200ms - [ ] Errors state what to do and provide the control to do it - [ ] No raw status codes or stack traces surfaced to the user - [ ] Forms preserve input on error - [ ] Validation on blur, not on keystroke - [ ] Inputs have correct `type`, `inputmode`, `autocomplete` - [ ] Button labels name their action, readable out of context - [ ] Destructive actions have undo or a specific confirmation - [ ] All five interactive states exist: rest, hover, active, focus-visible, disabled - [ ] Filter/tab/modal state is in the URL - [ ] Touch targets ≥ 44px, nothing hover-only ## Step 7: Accessibility Run the automated scan, then the keyboard walk. Full detail in `building-accessible-interfaces`. ```bash npx @axe-core/cli http://localhost:5173 --exit ``` - [ ] Zero axe violations, or each remaining one is justified in writing - [ ] Body text 4.5:1, large text 3:1 — **in both light and dark** - [ ] Focus visible everywhere; `outline: none` never appears without a replacement - [ ] Tab order matches visual order - [ ] Modals move focus in and return it on close - [ ] Every image has `alt` (empty for decorative) - [ ] Every icon-only button has an accessible name - [ ] One `