Skip to content
ExamsLibraryFor agentsConnect your agent
metaMeta: working with agents·17/26

Review checklist

Audit table — thirty-nine common UI issues, each with a grep and a fix. Scan against this before signing off.

Raw markdown On GitHub

A pre-flight checklist for UI code review. Scan the user's code against each row. If any row applies, write it up using review-format.

The checklist

#IssueQuick checkFixNode
1transition: allGrep for transition: all, Tailwind transition-all (and bare transition, which is all too)Specify transform, opacity (or other GPU-safe properties)transform-opacity-only
2scale(0) entranceGrep for scale(0), scale: 0Use scale(0.95) + opacitynever-scale-from-zero
3ease-in on UIGrep for ease-in (not ease-in-out)Use ease-out or custom cubic-beziereasing-curves
4Animation duration > 300ms on product UISearch durations in app routes and componentsMost UI is <300ms; exits shorter than entries; longer only for rare or marketing momentsduration-table
5Crossfade between two iconsTwo icons in same slot with opacity transitionsTransform a single icon (rotate, morph); unrelated glyphs cross-fade with scale 0.25 → 1 and blur 4px → 0 (interfaces-reconciliation)fly-not-teleport
6No prefers-reduced-motion guardGrep for @media (prefers-reduced-motionAdd for any translate/scale animationprefers-reduced-motion
7:hover on touchGrep for :hover (and Tailwind hover: with motion) without a media query gateWrap hover-driven motion in @media (hover: hover) and (pointer: fine)hover-states-subtle
8Default Bootstrap shadowGrep for box-shadow: 0 2px (or similar)Layered shadows at 4–6% opacityshadows-whisper
9Pure #000 dark modeGrep for background: #000 (or black)Use #18181b or #1a1a1adark-mode
10Loading indicator that flashes, or never comes<Spinner /> rendered immediately on async calls; a page-level spinner; a wait with no indicator at allDelay any indicator ~150ms, show something by 400ms; skeletons for content, spinners only inside controlsempty-loading-states
11Inter / SF Pro on marketing pageCheck font-family on landing/marketing routesUse a less-default sans (Geist, Pangram, Displaay)typography-humanity
12Sound with no mute, or on by defaultGrep for AudioContext, new Audio(, useSound; look for a persisted toggleOff by default, discoverable toggle, persisted; never sound-only informationsound-playback-web
13Sound on hover / focus / keystroke, or on page loadGrep for play( inside mouseenter, focus, keydown, or module scopeDelete it; sound belongs to daily and rare moments onlysound-decision-framework
14Gradient textGrep bg-clip-text, background-clip: text, text-transparentSolid ink; let size and weight carry emphasisai-default-tells
15Tiny tracked caps (eyebrows, kickers)One class string with uppercase + tracking-wide* + text-xs; class names eyebrow, kicker, overlineDelete the label, or fold its words into the headingline-length-tracking
16The generated-page purpleTailwind (bg|text|from|to|via|border|ring)-(purple|violet|indigo|fuchsia)-NNN; #6366f1 #8b5cf6 #7c3aed #a855f7 #4f46e5 #9333eaA colour from the contract or the brand; a .design or pov colour that happens to be violet winsai-default-tells
17Framework default status colours#3b82f6 #2563eb #10b981 #22c55e #ef4444 #f59e0bTake accent and status colours from the scalecolor-scales-oklch
18Emoji as UICode points U+1F300–1FAFF, U+2600–27BF in markup or copyAn icon from the set, or nothing; pov allows one in a notificationicon-systems
19Placeholder data shippedAcme, John Doe / Jane Doe, example.com, 123 Main St, lorem ipsumBelievable names, amounts that add up, real unitscontent-authenticity
20100vh / h-screenGrep h-screen, height: 100vh100svh / 100dvh; size a section to its contentviewport-custom-design
21Looping attention animationanimate-(bounce|ping|pulse)Delete; a pulse is for a live state onlyanimation-decision-framework
22Em-dash habitMore than ~4 — across the page's copyA full stop or a comma; one or two a pagecopy-tells
23A custom widget with no keyboardrole="button", role="tab", role="menuitem" with no onKeyDown; <div onClick; a positive tabIndexThe native element, or the APG key map; tabindex 0 or −1 onlykeyboard-patterns
24A live region mounted with its text{msg && <div role="status", or aria-live="assertive" on a success toastRender the region empty first, then set its text; success is role="status"screen-reader-announcements
25Submit disabled until validdisabled={!isValid} on a submit buttonKeep it enabled; validate on submit, focus the first invalid fieldforms-validation
26Primitive colour in a componentvar(--blue-600) or bg-blue-600 in a component file; --color-primary beside --color-text-primaryA semantic role token; the brand is accentcolor-token-tiers
27Input text under 16px on phonestext-sm/text-xs or font-size < 16px on input, textarea, select with no mobile override; maximum-scale=1 in the viewport metatext-base md:text-sm; never lock zoomline-behavior
28Arbitrary z-indexz-index: 9999, z-50 scattered, any value not on the scaleA fixed scale, or isolation: isolatez-index-strategy
29Weight changes on hover or selectedhover:font-semibold, :hover { font-weight, .active { font-weightHold weight; signal with colour or an indicatortype-scale-and-rhythm
30Icon-only button with no name<button> whose only child is an <svg>/icon, with no aria-label or visually-hidden textAn aria-label naming the action ("Search", not "icon")accessibility-baseline
31Core action reachable only on hoverActions inside group-hover:/:hover reveals with no focus or touch routeVisible on :focus-within and on touch, or always visibletouch-and-focus
32Focus ring removedoutline: none / outline-none with no :focus-visible replacementA neutral :focus-visible outline with outline-offset: 2pxaccessibility-baseline
33Faked bold or italicNo font-synthesis: none at the root; weights requested that aren't loadedDeclare it; load the weights you usetype-scale-and-rhythm
34Layout properties animatedtransition or keyframes on width, height, top, left, margintransform and opacitytransform-opacity-only
35Unbounded list, no virtualization.map( over fetched or user-growing arrays with no windowingVirtualize past ~50 rowsperformance-discipline
36Disabled by opacityopacity-50/opacity: .5 on :disabled or disabled:Dedicated disabled tokenscontrast-and-color-scheme
37Undesigned scheme declaredcolor-scheme: light dark or a dark theme-color on an app with no dark themeDeclare only the schemes that existcontrast-and-color-scheme
38Theme-blind SVGfill="#fff", fill="#000", stroke="white" inline in icons or logoscurrentColor or a CSS variabledark-mode
39Truncation with no way outtruncate, line-clamp-*, text-overflow: ellipsis on meaningful text with no title, tooltip or expandGive the full string a routeline-behavior

Workflow

  1. Read the code top to bottom once for context.
  2. Run through the checklist row by row, noting which apply.
  3. Open ai-default-tells for any visible defaults beyond this checklist.
  4. Write the review using review-format (Before / After / Why table), one row per applicable issue.
  5. If nothing applies — say so. "Looks good, nothing to change" is a valid review.

When to use

Every UI code review. This is the systematic pass before the taste-driven pass. A launch film is not UI code: review it with launch-video-review.

Gotcha

Don't run the checklist mechanically without context. Item 4 ("duration > 400ms") is fine for rare-moment animations (see delight-impact-curve) — it's a flag, not a rule. Always check the context before writing the row.

Also: the checklist is not exhaustive. The most valuable reviews catch what's not on this list. Use it as a floor, not a ceiling.

Sources

  • Emil Kowalski — Review Checklist section of emilkowalski/skill.
  • Jakub Krehel, interfaces skills (MIT, HEAD 592aedc): the Before-you-finish tables in better-accessibility, better-colors and better-ui — rows 23–26 and the row-5 fix.
  • Leonxlnx, taste-skill v2.1.1 (MIT, HEAD a9c73c3): skills/taste-skill/scripts/lint.mjs — the patterns behind rows 14–22. Its mono, numbering, hairline, shadow and terracotta rules are its owner's taste and are not adopted (references/research.md §6).
  • Related: review-format for the output format, ai-default-tells for broader visual audit.

metareviewchecklist