Run one question over the whole surface: could this have been produced in one prompt with nothing specified? If yes, the tells below are why. This is not an anti-AI scan — plenty of generated UI is excellent, because whoever prompted it specified the type, the palette, the elevation ladder, and the curves. The tells appear where nobody decided anything, so every fix is an opinion, not an avoidance. Default posture: fix in place. A tell has exactly one correct replacement and no interesting argument attached to it, so editing the file is cheaper and more honest than writing a report and waiting.
Two siblings share the neighbourhood and the boundary is the shape of the answer. ui-review promises a verdict over every dimension of a diff; this skill answers one question against a fixed, greppable list and returns a patch rather than a judgment. design-critique teaches a looking order that finds things no list contains; this one matches literal tokens. If the user asked "is this well built?" they are not asking this skill.
Before replacing anything, find the project's own answer. The replacement values here are house defaults for a codebase with no system. Grep the token file, the Tailwind theme, the CSS custom properties, and package.json first: if the project already defines an accent, a shadow ladder, a radius scale, or an easing set, the replacement is the project's token and the raw value in the catalog is wrong for this repo. Never resolve a tell by introducing a second styling system alongside the one already there.
Quick reference
| Topic | File | Open it when |
|---|---|---|
| The full catalog — grep pattern, why it reads as generated, replacement value | references/tells.md |
Open it at the start of every scan; it is the list the scan runs against, and the replacement column is the only place the exact values live. |
The scan, in four passes
Run them in order. Each pass is cheaper than the next and catches things the next one would let through.
| Pass | What you do | Judgment required |
|---|---|---|
| 1. Grep | Match the literal token set from references/tells.md |
None — a hit is a hit |
| 2. Strings | Read every user-visible string: headings, CTAs, empty states, errors, numbers, names | Low |
| 3. Composition | Read the page skeleton, the radius set, the accent count, the section pattern | Medium |
| 4. Decisions | Ask what was chosen here that a default would not have chosen | High — this is the real test |
Pass 4 is the frame restated. A page can survive passes 1–3 and still fail: identical card treatment for unrelated content, every route on the same template, a hero followed by three feature cards followed by an FAQ. Nothing there is technically wrong. Nothing there was decided either.
Core principles
-
Grep before you look. The literal set is unambiguous and costs one pass:
transition: all,scale(0),ease-inon a UI element,transform-origin: centeron a popover, an animation on a keyboard-initiated action,#000orblackas a dark background,#fffas text on dark,0 2px 8px rgba(0,0,0,0.1), violet/indigo/fuchsia gradient utilities,lorem,John Doe,Acme. Exception: a deliberate#000on an OLED-targeted app is a committed choice — confirm it is committed before replacing it. -
Fix in place, do not report. Each tell has one replacement, so a report just adds a round trip before the same edit happens. Exception: when the replacement needs a decision the project has never made — picking a display typeface, choosing the brand accent — leave the tell, and say in one line which decision is missing.
-
Replace the value, never the system. The fix for a violet gradient hero is one solid accent from the project's ramp, not a new palette; the fix for a Bootstrap shadow is a layered shadow at
4–6%opacity on a#111base, not an elevation redesign. Exception: if grep finds no scale of any kind, the smallest honest fix is to introduce one scale for that one dimension and say so. -
One saturated accent in the chrome. More than one accent is the loudest visual tell, because a default palette hands you three and a decision keeps one. The house replacement set: a warm off-white
#fdfdfcin place of#fff, a grey ladder withrgba(0,0,0,.4)as the workhorse secondary in place of a second hue, and one saturated accent everywhere else —colorowns the ramp these come from. Exception: semantic status colors — success, warning, danger — are not accents and do not count against the budget. -
Rounded numbers and placeholder names are content tells, and content tells are the loudest.
100+ customersreads as an approximation because it is one;127 customersimplies somebody looked at the data. Same forJohn Doe,Acme Inc., anduser@example.com. Exception: never invent a specific number to replace a vague one — if the real figure is unknown, delete the claim instead of fabricating precision. -
Uniform radius is a tell, and so is random radius. A single value applied to every element from the page shell down to the badge means the radius was never chosen. The fix is a small scale with concentric nesting: inner radius = outer radius − padding. Exception: a fully-round pill or avatar sits outside the scale and stays that way.
-
Emoji as list markers, in headings, or inside button labels is a tell. Emoji carry their own metrics — they ignore the inherited font weight and color and sit on their own optical baseline — so a list bulleted with them breaks vertical rhythm in a way no designed marker does. Replace with a real list marker or an icon from the project's set, sized to the type. Exception: user-authored content, where the emoji is data and not design.
-
A tell is a flag, not a verdict. Some entries are correct in context: a long duration on a rare celebration, a spinner on a genuinely slow call, an asymmetric grid on a marketing page. Check the context before editing. Exception: the five lint-level prohibitions in pass 1 have no legitimate context and are replaced unconditionally.
Smells
| Smell | Fix |
|---|---|
| Violet → indigo → fuchsia gradient on the hero | One solid accent from the project's ramp |
background: #000 in dark mode |
#18181b, or the project's darkest surface token |
color: #fff on a dark surface |
rgba(255,255,255,0.92) |
box-shadow: 0 2px 8px rgba(0,0,0,0.1) |
Layered shadows at 4–6% opacity on a #111 base |
Same border-radius on every element |
A radius scale; nest concentrically |
| Lorem ipsum, "Coming soon", "Section title" | Real copy, or delete the section |
| "Get started" as the primary CTA | The actual verb: "Create project", "Import repo" |
| "Oops! Something went wrong" | What happened and what to do: "Couldn't save. Try again." |
| Crossfade between two icons | Transform one icon; iconography owns the shape constraint |
Spinner on a sub-800ms call |
Nothing; ui-states owns the threshold ladder |
| Emoji bullets in a feature list | The project's icon set at the type's optical size |
Output format
Apply the edits, then report in two short lists. Replaced — one line per tell: the pattern, the file, and the value it became. Left alone — one line per tell that survived, naming the context that justified it or the decision the project still owes. No table, no verdict, no severity ranking; if the user needs those, they wanted ui-review.
Checklist
- Project tokens grepped before any replacement value was chosen
- Pass 1 literal set grepped across the whole surface, not just the changed files
- Every user-visible string read, including empty and error states
- Accent count checked — one saturated accent in the chrome
- Radius set checked for uniformity and for concentric nesting
- Emoji markers, headings, and button labels checked
- Every replacement is the project's own token where one exists
- Edits applied in place, not described
- Survivors listed with the context or the missing decision that spared them
- No number invented to replace a vague one