-
Notifications
You must be signed in to change notification settings - Fork 6
Anti Patterns Reference
This page documents the 152 anti-pattern rules that power the ux-skill linter. Each rule encodes one tell of AI-generated UI: a specific, repeatable choice that unconstrained models reach for by default, which makes their output recognizable as machine-made. The linter is deterministic and regex-based, runs with no model call, and exits non-zero on Critical and High findings, so it is safe to put in CI.
The rule data lives in data/anti-patterns.json and is the canonical source. The figures and examples below are taken from that file at v3.1.0. For the prose behind the rules, see https://uxskill.laithjunaidy.com/anti-patterns.html. To run the linter, see Getting Started.
The phrase "looks AI-generated" sounds subjective, but in practice it is a short list of defaults. When a model is asked to design with no constraints, it optimizes for the safest, most-represented choice in its training distribution. Across millions of prompts that converges on the same handful of moves, so the output becomes recognizable not because any single choice is bad but because it is the default choice, repeated everywhere.
That makes the problem tractable. If the tells are specific patterns, you can detect them with patterns. The linter does exactly that: 152 rules, each with a detection signature, the reason it reads as slop, and the fix that gets you off the default path.
Rules are grouped into nine categories. The counts below are exact at v3.1.0.
| Category | Rules | What it covers |
|---|---|---|
| A11y | 41 | Accessibility failures: missing alt text, focus removed without replacement, click handlers on non-interactive elements, blocked zoom |
| Content | 33 | Placeholder names, lorem ipsum, emoji in UI, fake testimonials, filler marketing verbs, round-number stats |
| Layout | 15 | Three-equal-card grids, centered-everything heroes, pill-rounded everything, fake logo clouds |
| Typography | 14 | Inter as a display face, system-font-only stacks, arbitrary hero pixel sizes, title-case headlines |
| Visual | 13 | Frosted-glass on every surface, multi-layer default shadows, decorative blur, emoji as icons |
| Quality | 12 | Inline styles, leaked console logs, TODO and FIXME comments, any types, z-index: 9999, placeholder class names |
| Motion | 10 | The 300ms default transition, bouncing-arrow CTAs, transition: all, animating layout properties |
| Color | 9 | The purple-to-blue gradient, rainbow gradient text, purple card glows, vague Tailwind color names |
| Performance | 5 | Images without dimensions, JPG where WebP or AVIF belongs, render-blocking CSS imports, non-passive scroll listeners |
| Total | 152 |
Every rule carries one of four severities. The split at v3.1.0 is 6 Critical, 52 High, 67 Medium, 27 Low.
- Critical (6). Hard failures that break the product for real users, all of them accessibility violations. The linter is built to fail CI on these.
- High (52). Strong slop fingerprints or serious accessibility and quality issues. Also fails CI by default.
- Medium (67). Recognizable tells worth fixing, but not blocking on their own.
- Low (27). Minor signals and polish items.
In CI, --fail-on high fails the job on Critical and High while letting Medium and Low through as warnings. Tune the threshold to your tolerance.
These are the hard failures. Every one is an accessibility violation that makes the interface unusable for someone.
| Rule id | Issue |
|---|---|
viewport-no-zoom |
Viewport meta blocks pinch-zoom, trapping low-vision users |
outline-none-no-focus-visible |
Focus outline removed with no :focus-visible replacement, so keyboard users lose their place |
div-onclick-no-role |
Click handler on a <div> with no role or tabindex, invisible to keyboard and assistive tech |
aria-hidden-on-interactive |
aria-hidden on a focusable interactive element, hiding it from screen readers while keeping it tabbable |
blink-tag |
A <blink> element, which is both deprecated and a motion hazard |
onclick-on-non-button |
onclick on a non-interactive element, the same keyboard trap as div-onclick-no-role
|
Example, verbatim from the rule data:
div-onclick-no-role(Critical, A11y). A<div onClick>is invisible to keyboard users and assistive tech, the action exists only for mouse users, a WCAG 2.1.1 and 4.1.2 failure. Fix: Use<button onClick>for actions. If you must keep the<div>, addrole="button",tabindex="0", and anonKeyDownhandler for Enter and Space.
The point of a rule is not just to flag a pattern but to explain why it reads as slop and what to do instead. Here are seven, one per several categories, quoted from the rule data.
purple-to-blue-gradient(High). Purple-to-blue gradient on white is the strongest visual fingerprint of unconstrained model output and reads as template-marketplace AI slop. Fix: Use a single restrained accent (Emerald, Electric Blue, Deep Rose, Amber) against neutrals; keep gradient hue spread under 60 degrees if used at all.
inter-as-display(High). Inter is a body font tuned for screen legibility at small sizes; deployed as display it reads as the default startup-landing fingerprint. Fix: Pair Inter (body) with a distinctive display face: Geist, Satoshi, Cabinet Grotesk, General Sans, Outfit, or a brand-specific variable sans.
three-equal-card-grid(High). Three equal cards with three icons and three short paragraphs is the safest default the generator reaches for and the strongest layout fingerprint in AI-generated marketing surfaces. Fix: Use asymmetric layouts: bento grids, 2-and-1 splits, 4 with one spanning width. Vary card size by content priority.
centered-everything-hero(Medium). Centered headline plus centered subtitle plus centered button stack is the laziest hero composition and signals the generator picked it when it couldn't find a better layout. Fix: Use a left-aligned hero with an editorial 7-5 or 8-4 grid split; let the imagery earn its own column.
fake-name-john-doe(Medium). John and Jane Doe and their cousins signal that nobody thought about who would actually use the product, an immediate AI-generated-tutorial vibe. Fix: Use plausible names that fit the target market: Maya Iqbal, Adam Levin, Wen Zhang, Layla Haddad. Match region for regional products.
cta-arrow-rightward-bouncing(Medium). A bouncing arrow on a CTA is the desperate-attention pattern that signals the copy itself isn't doing the work. Fix: Let the CTA copy carry the action (Start a 14-day trial, Read the deployment guide). If you need an arrow, use a static SVG that nudges 2 to 4px on hover.
glass-morphism-default(Medium). Frosted-glass everywhere is the iOS-aesthetic AI tell, signaling the generator confused depth with blur on every surface. Four or more backdrop-blur layers on a single page is the visual equivalent of using bold on every word. Fix: Reserve glass for one or two surfaces that have a clear reason to feel translucent (a floating nav, a notification toast). Solid surfaces with restraint read more premium than four blurred panels.
The linter reports findings; it does not edit your files. Each finding includes the location, the rule id, the severity, and the evidence that triggered the match.
# Python entry point (preferred)
uxskill lint .
# or the script directly
python3 bin/ux-lint.py .
# CI mode, fail on Critical and High
bash bin/ux-lint.sh --ci --fail-on highIn a GitHub Actions workflow:
- name: ux-lint
run: bash bin/ux-lint.sh --ci --fail-on highTo act on findings rather than just read them, chain into a fix pass: /ux-polish --fix for a cosmetic sweep on the same patterns, or /ux-fix to apply findings as atomic commits sorted by severity.
The 152 rules are not only a post-hoc check. The same anti-pattern data feeds the recommender: when you run ux recommend, all 152 guardrails are activated as part of the recommended system, and your forbidden moves narrow the brand-exemplar shortlist before any code is generated. So the rules act twice, once as direction going into generation and again as verification coming out. That closed loop is the difference between hoping the output is clean and proving it.
New rules are among the most useful contributions to the project. A good rule has a precise detection signature (so it does not fire on legitimate code), a clear statement of why the pattern reads as slop, and an actionable fix. False-positive reports are equally valuable: if a rule fires on correct code, that is a bug worth an issue. File either at https://github.com/Laith0003/ux-skill/issues.
- Getting Started for install and the discover to recommend to design to lint loop.
- Home for what ux-skill is and how the manifests fit together.
- The rule prose on the site: https://uxskill.laithjunaidy.com/anti-patterns.html
- The README, which is the exhaustive reference: https://github.com/Laith0003/ux-skill/blob/main/README.md