Spotting Machine-Generated UI
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-in on a UI element, transform-origin: center on a popover, an animation on a keyboard-initiated action, #000 or black as a dark background, #fff as text on dark, 0 2px 8px rgba(0,0,0,0.1), violet/indigo/fuchsia gradient utilities, lorem, John Doe, Acme. Exception: a deliberate #000 on 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 #111 base, 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 #fdfdfc in place of #fff, a grey ladder with rgba(0,0,0,.4) as the workhorse secondary in place of a second hue, and one saturated accent everywhere else — color owns 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+ customers reads as an approximation because it is one; 127 customers implies somebody looked at the data. Same for John Doe, Acme Inc., and user@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
1---2name: ai-tells3description: Use when checking whether a UI looks machine-generated: `transition: all`, `scale(0)`, pure-black shadows, default violet gradients, uniform radii, and emoji bullets.4---567# Spotting Machine-Generated UI89Run 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.1011Two 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.1213**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.1415## Quick reference1617| Topic | File | Open it when |18|---|---|---|19| 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. |2021## The scan, in four passes2223Run them in order. Each pass is cheaper than the next and catches things the next one would let through.2425| Pass | What you do | Judgment required |26|---|---|---|27| 1. Grep | Match the literal token set from `references/tells.md` | None — a hit is a hit |28| 2. Strings | Read every user-visible string: headings, CTAs, empty states, errors, numbers, names | Low |29| 3. Composition | Read the page skeleton, the radius set, the accent count, the section pattern | Medium |30| 4. Decisions | Ask what was *chosen* here that a default would not have chosen | High — this is the real test |3132Pass 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.3334## Core principles35361. **Grep before you look.** The literal set is unambiguous and costs one pass: `transition: all`, `scale(0)`, `ease-in` on a UI element, `transform-origin: center` on a popover, an animation on a keyboard-initiated action, `#000` or `black` as a dark background, `#fff` as text on dark, `0 2px 8px rgba(0,0,0,0.1)`, violet/indigo/fuchsia gradient utilities, `lorem`, `John Doe`, `Acme`. *Exception:* a deliberate `#000` on an OLED-targeted app is a committed choice — confirm it is committed before replacing it.37382. **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.39403. **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 `#111` base, 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.41424. **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 `#fdfdfc` in place of `#fff`, a grey ladder with `rgba(0,0,0,.4)` as the workhorse secondary in place of a second hue, and one saturated accent everywhere else — `color` owns the ramp these come from. *Exception:* semantic status colors — success, warning, danger — are not accents and do not count against the budget.43445. **Rounded numbers and placeholder names are content tells, and content tells are the loudest.** `100+ customers` reads as an approximation because it is one; `127 customers` implies somebody looked at the data. Same for `John Doe`, `Acme Inc.`, and `user@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.45466. **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.47487. **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.49508. **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.5152## Smells5354| Smell | Fix |55|---|---|56| Violet → indigo → fuchsia gradient on the hero | One solid accent from the project's ramp |57| `background: #000` in dark mode | `#18181b`, or the project's darkest surface token |58| `color: #fff` on a dark surface | `rgba(255,255,255,0.92)` |59| `box-shadow: 0 2px 8px rgba(0,0,0,0.1)` | Layered shadows at `4–6%` opacity on a `#111` base |60| Same `border-radius` on every element | A radius scale; nest concentrically |61| Lorem ipsum, "Coming soon", "Section title" | Real copy, or delete the section |62| "Get started" as the primary CTA | The actual verb: "Create project", "Import repo" |63| "Oops! Something went wrong" | What happened and what to do: "Couldn't save. Try again." |64| Crossfade between two icons | Transform one icon; `iconography` owns the shape constraint |65| Spinner on a sub-`800ms` call | Nothing; `ui-states` owns the threshold ladder |66| Emoji bullets in a feature list | The project's icon set at the type's optical size |6768## Output format6970Apply 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`.7172## Checklist7374- [ ] Project tokens grepped before any replacement value was chosen75- [ ] Pass 1 literal set grepped across the whole surface, not just the changed files76- [ ] Every user-visible string read, including empty and error states77- [ ] Accent count checked — one saturated accent in the chrome78- [ ] Radius set checked for uniformity and for concentric nesting79- [ ] Emoji markers, headings, and button labels checked80- [ ] Every replacement is the project's own token where one exists81- [ ] Edits applied in place, not described82- [ ] Survivors listed with the context or the missing decision that spared them83- [ ] No number invented to replace a vague one