Skip to content

Anti Pattern Catalog 100

Laith0003 edited this page May 28, 2026 · 1 revision

Anti-Pattern Catalog — all 100 rules

This is the full indexed catalog of the 100 anti-pattern rules that ship in data/anti-patterns.json. Every rule has a stable id, a severity, a category, a one-line "why," and a one-line "fix." The linter walks this catalog on every ux lint run.

For how the linter works, severity ramps, and CI integration, see Linter Rules. For the engine that loads the catalog into every recommendation as active guardrails, see Recommender Engine.


How to read this page

The catalog is grouped by category in descending count order. Each section header carries the running total — ## A11y (23 rules — 1–23 of 100) means rules 1 through 23 are in the A11y bucket. The categories add to 100.

For every rule:

  • # — its position in the catalog (1–100).
  • ID — kebab-case, stable across versions. Quote it in CI annotations, in --rule flags, in PR comments.
  • Severity — one of critical (4), high (33), medium (42), low (21). See Linter Rules for what each level means.
  • Name — what the linter prints in findings.
  • Why — the load-bearing argument. One sentence. The reason this is bad enough to ship as a rule.
  • Fix — the do-instead. One sentence. Concrete enough to action.

For the full regex, scope list, and flag string of every rule, read data/anti-patterns.json — every entry there has a detection.pattern, detection.flags, and detection.scope field.


Severity ramp

Severity Count Share CI behavior at default --threshold high
critical 4 4% Always fails CI
high 33 33% Fails CI
medium 42 42% Reported, doesn't fail CI
low 21 21% Reported, doesn't fail CI

The 4 critical rules are the WCAG-violation ones — pinch-zoom blocked, focus rings removed without :focus-visible, click handler on a <div> without role, aria-hidden on a focusable interactive element. These never ship.


Category breakdown

Category Count Share Range
A11y 23 23% 1–23
Content 15 15% 24–38
Layout 13 13% 39–51
Typography 10 10% 52–61
Color 9 9% 62–70
Visual 9 9% 71–79
Quality 9 9% 80–88
Motion 8 8% 89–96
Performance 4 4% 97–100

A11y leads (23%) because accessibility is the most-frequently broken thing in AI-generated UI and the easiest to detect with regex. Content (15%) is the second-largest category — placeholder names, lorem ipsum, "Elevate / Seamless / Unleash" marketing verbs, suspiciously round numbers. Together A11y and Content account for over a third of the catalog.


A11y (23 rules — 1–23 of 100)

# ID Severity Name Why Fix
1 inline-svg-no-aria high SVG without aria-label or aria-hidden SVG with no aria-label and no aria-hidden is announced by screen readers as 'graphic' with no context - failure of WCAG 1.1.1. Decorative SVG gets aria-hidden="true".
2 button-no-type medium Button missing type attribute A inside a without type="button" defaults to type="submit" and silently submits the form on every click. Always set type="button" or type="submit" explicitly.
3 img-no-alt high Image missing alt attribute Missing alt breaks screen readers, SEO image indexing, and the broken-image fallback all at once - WCAG 1.1.1 failure. Every gets alt. Decorative images get alt="" (empty). Informative images get a description a screen reader user would need.
4 link-onclick-no-href high Anchor with onClick but no href An without href is not focusable by default and is invisible to assistive tech - it looks like a link but behaves like a button. Use for actions. Use for navigation. Never an anchor without href as a click target.
5 heading-skip-h1-h3 medium Skipped heading level Heading hierarchy gaps (h1 then h3, or h3 with no h1/h2 preceding) break the screen-reader landmark tree and the SEO outline. h1 once per page; nest h2/h3/h4 in order.
6 infinite-scroll-no-pagination medium Infinite scroll without keyboard fallback Pure infinite scroll with no Load more button locks out keyboard users and breaks back-button history - WCAG 2.4 failure plus a usability one. Ship infinite scroll AND a visible Load more button.
7 viewport-no-zoom critical Viewport blocks pinch-zoom user-scalable=no blocks pinch-zoom for low-vision users on touch devices - a WCAG 1.4.4 failure that locks out anyone who needs to enlarge text. Remove user-scalable=no entirely, or set it to yes.
8 outline-none-no-focus-visible critical outline removed without focus-visible replacement outline: none removes the only keyboard focus indicator and leaves keyboard users with no signal of focus state - a WCAG 2.4.7 failure. Pair outline: none with a :focus-visible rule that restores a visible ring: :focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }.
9 placeholder-as-label high Input placeholder used as the only label Placeholders disappear on focus, sit at low contrast by default, and are not announced consistently by assistive tech - using one as the only label fails WCAG 3.3.2. Always pair with a or aria-label.
10 div-onclick-no-role critical Click handler on
without role or tabindex
A
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.
Use for actions.
11 anchor-no-href-as-button high Anchor styled as button without href An without href is not focusable, not in the tab order, and not announced as a link - looks like a button but behaves like nothing. Use for actions, for navigation.
12 cursor-pointer-non-interactive medium cursor:pointer on non-interactive element A
or with cursor: pointer promises clickable behavior to sighted mouse users while keyboard and screen-reader users get nothing - a hidden interaction.
If it clicks, make it a or .
13 tooltip-on-required-info high Load-bearing copy hidden in a title attribute A title attribute carrying 60+ characters of real content is hover-only - invisible on touch, inconsistently announced by screen readers, undiscoverable on keyboard. Move the content into the visible flow or into an aria-describedby popover that is reachable by keyboard and touch.
14 scroll-to-top-button-everywhere low Fixed scroll-to-top button on short pages A floating back-to-top button on a page under four viewports adds permanent visual noise for a vanishingly rare interaction - signals the generator included the pattern as decoration, not need. Drop the button. If pages can grow past four viewports, gate the button on scroll position and hide it below threshold. Keyboard users have Home; mouse users have the wheel.
15 hover-only-card-actions high Card actions revealed only on hover Card actions hidden at opacity: 0 and revealed only on :hover are invisible to touch users, keyboard users, and anyone using assistive tech - the action does not exist for most of your audience. Show card actions persistently at reduced visual weight (lower opacity, smaller scale, secondary color), or make them reachable via a visible focus-state.
16 aria-hidden-on-interactive critical aria-hidden on a focusable interactive element aria-hidden="true" on a focusable element (button, link, input) removes it from the accessibility tree while leaving it in the tab order - keyboard users can land on a control that screen readers refuse to announce. If the control is decorative, remove it from the DOM or set tabindex="-1" AND aria-hidden together.
17 target-blank-no-noopener high target="_blank" without rel="noopener noreferrer" target="_blank" without rel="noopener" lets the opened page access window.opener and rewrite the source URL - a tabnabbing vector. Always pair target="_blank" with rel="noopener noreferrer".
18 safe-area-inset-only-bottom medium safe-area-inset-bottom without top/left/right siblings Reaching only for env(safe-area-inset-bottom) signals the dev copied the iPhone-X home-indicator fix without thinking about the notch (top), Dynamic Island, landscape mode, or rotated devices where left/right inset matters. If the surface goes edge-to-edge, set all four insets (or padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left) and let CSS pick the active ones).
19 overflow-hidden-on-html high overflow: hidden applied to overflow: hidden on the root element breaks browser scroll restoration on back-navigation, disables 'skip to content' anchor scrolling for screen-reader users, kills smooth-scroll APIs, and prevents the URL bar from collapsing on mobile. Find the actual overflowing child (often a 100vw image, fixed-position element, or oversized inline-block) and constrain it.
20 lang-attribute-missing high without lang attribute without a lang attribute leaves screen readers guessing which pronunciation engine to use - Arabic content gets read in English phonemes, English headings get read with Spanish stress patterns, and automatic translation breaks. Add lang="en" (or your primary locale) to .
21 cursor-not-allowed-no-visual high cursor: not-allowed with no visible disabled state cursor: not-allowed in a rule that has no opacity reduction, no muted color, no border tweak, and no pointer-events: none signals the disabled state was set by an AI that knew the cursor convention but never designed the actual visual. A disabled control needs three signals: visual (opacity 0.5 + maybe muted color or removed border), behavioral (pointer-events: none, or aria-disabled='true' + handle the click), and announcement (aria-disabled on non-button elements; the disabled attribute on actual buttons does both).
22 screen-reader-only-without-class medium Skip-to-content link with no sr-only / visually-hidden class A 'Skip to content' link without an .sr-only / .visually-hidden / .skip-link class fallback is visually shown to all users as a stray link at the top-left of the page - signals the dev knew the accessibility convention by name but didn't apply the actual technique. Wrap with the standard sr-only pattern: Skip to content.
23 pointer-events-none-on-link high with pointer-events: none and no aria-disabled An tag with pointer-events: none and no aria-disabled='true' is announced to screen readers as a fully working link, takes a position in the tab order, and gives keyboard users an Enter-key path to nothing - the click is silently swallowed. If the link is permanently inactive, render a with the same visual treatment instead of an .

Content (15 rules — 24–38 of 100)

# ID Severity Name Why Fix
24 fake-name-john-doe medium Generic placeholder names John/Jane Doe and their cousins signal that nobody thought about who would actually use the product - immediate AI-generated-tutorial vibe. Use plausible names that fit the target market: Maya Iqbal, Adam Levin, Wen Zhang, Layla Haddad.
25 lorem-ipsum-leak high Lorem ipsum in shipping code Lorem ipsum in production source code is unshipped placeholder content - the most obvious draft-state leak. Write real copy that makes a specific claim about the product.
26 emoji-in-ui high Emoji used as UI element Emojis render inconsistently across platforms, ignore brand color, and signal informality where SVG icons signal craft. Inline SVG from Lucide, Feather, Phosphor, or Heroicons at 1.5-2px stroke with currentColor.
27 icon-emoji-stamp high Emoji used as icon stamp Emoji where an SVG icon belongs breaks brand color, scales poorly, and renders differently on every OS. Inline SVG icon with currentColor and a consistent stroke width across the surface.
28 testimonial-fake-five-stars high Hardcoded five-star testimonial Five hardcoded stars next to a fake quote is the lowest-trust pattern on the web; users have learned to discount it on sight. Show real, named, attributable testimonials with company affiliation and a specific outcome.
29 filler-marketing-verbs high Filler marketing verb in headline Elevate, Seamless, Unleash, Revolutionize, Empower, Supercharge, Transform - the unmistakable fingerprint of unedited marketing copy generated without a real product in mind. Name the specific action and the specific outcome.
30 generic-cta-text high Generic CTA text (Click here, Learn more) Click here / Learn more / Get started reveals nothing about the destination and fails screen-reader scan-by-link - users hear a list of identical labels with no context. Name the specific action: Read pricing, Start free trial, View dashboard, Open API docs.
31 round-number-stats medium Round number marketing stat 99.99%, 10x faster, 100% guaranteed - the suspiciously round numbers AI invents to sound credible. Use the real number you measured: 99.93% uptime over Q1, 4.2x faster median build, refunded 218 of 244 reported issues.
32 placeholder-as-pricing high Default AI pricing tier amounts $9 / $19 / $29 / $49 / $99 / $199 are the prices AI reaches for when no real pricing exists - unconvincing placeholders that signal nobody priced the product. Use the real price you charge, with the real currency and the real billing cadence.
33 trust-badge-no-source medium Unsourced trust-by claim Trusted by 1000+ teams / Used by Fortune 500 / Loved by 50k developers - unsourced trust claims that signal the generator invented the number to fill space. Name the actual companies, link to the actual case studies, or show the actual review aggregate.
34 timestamp-just-now medium Fake just-now timestamp in static markup Just now / moments ago in static HTML is a fake activity signal - it stays Just now forever and signals nobody wired the real time-ago helper. Render real timestamps via a time-ago helper bound to a real source (Date.now, server time, last_event_at).
35 fake-line-of-code-count medium Unsourced large-number marketing claim We saved 10,000 hours / Generated 1,000,000 lines of code / Reduced 50,000 tickets - large round numbers with no citation that signal the magnitude was invented to sound impressive. Use the real measured number with the real source: 'In Q1 2026, customers saved 4,217 support hours (mean across 38 teams)'.
36 before-after-100-percent medium Magnitude-default before-after claim 0 to 100 / 100% guaranteed / 100% effective are the magnitude-by-default phrases AI reaches for - readers have learned to discount them. Quote the real spread: 'from 14 manual steps to 3 automated'.
37 unsplash-photo-id-no-alt high Unsplash photo URL with empty or missing alt An images.unsplash.com/photo-* URL with no alt (or alt="") is the stock-photo placeholder fingerprint - signals the generator pasted the first credible image without choosing it for the content. Replace with a real product or brand image and write an alt that describes the content (alt="Customer service team reviewing a dashboard").
38 version-1-0-0-evergreen low Hardcoded v1.0.0 in nav or footer A static 'v1.0.0' in the nav or footer never updates with the real build - signals the generator added a version chip as decoration without wiring it to package.json or the deploy SHA. Either pull the version from package.json at build time (Vite/Webpack define), bind it to the deploy SHA, or remove the chip entirely.

Layout (13 rules — 39–51 of 100)

# ID Severity Name Why Fix
39 three-equal-card-grid high Three equal cards in a row 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. Use asymmetric layouts: bento grids, 2-and-1 splits, 4 with one spanning width.
40 centered-everything-hero medium Centered hero composition Centered headline + centered subtitle + centered button stack is the laziest hero composition and signals the generator picked it when it couldn't find a better layout. Use left-aligned hero with editorial 7-5 or 8-4 grid split; let the imagery earn its own column.
41 pill-rounded-full-everywhere low rounded-full applied to everything rounded-full on every button and input is the iOS-tutorial fingerprint - signals the radius decision was skipped entirely. rounded-full for pills, avatars, icon buttons only.
42 avatar-stack-overlapping medium Generic overlapping avatar stack Three overlapping circular avatars in the nav as 'our team' or 'join 10k users' is a content-marketing template tell. Use real customer logos, named testimonials with quotes, or specific signal (Used by 312 engineering teams at Series-A companies).
43 nav-equal-hamburger-desktop low Hamburger menu on desktop Hamburger on a wide viewport hides nav from users for no reason; signals the responsive breakpoint was skipped. Show inline nav on md: and up; hide the hamburger above that breakpoint with md:hidden.
44 h-screen-no-dvh-fallback high 100vh without 100dvh fallback for mobile 100vh on mobile includes the browser chrome that retracts on scroll, so the layout jumps as the address bar collapses - dynamic viewport units (dvh) fix this. Use height: 100dvh or h-[100dvh] as the primary, with h-screen / 100vh as the legacy fallback for browsers that lack dvh.
45 fixed-height-text-block medium Pixel height on a text container Fixing a pixel height on a container that also styles text breaks reflow at every other zoom level and font scale - the layout is one breakpoint away from clipping. Use min-height for floor protection, line-height + padding for vertical rhythm, and let content determine height.
46 logo-cloud-no-real-logos medium Logo cloud image with empty alt An inside a Trusted by / logo-cloud section with alt="" is an unsourced trust claim with no machine-readable company name - the logo is decorative even when the surface is selling credibility. Set alt to the real company name (alt="Stripe"), and only use the logo if you have permission.
47 aspect-ratio-1-1-default medium Square aspect-ratio as default everywhere Every image clipped to 1:1 is the Instagram-grid AI fingerprint - signals the generator picked the safest crop instead of letting content set the frame. Vary aspect ratio by content: 3:2 for product shots, 4:5 for portraits, 16:9 for hero or video.
48 grid-cols-3-1fr-default low repeat(3, 1fr) as a default grid repeat(3, 1fr) with no minmax(), no auto-fit, and no asymmetric weighting is the laziest grid declaration - signals the layout was set by a generator that defaulted to 'three equal columns' without thinking about content priority or responsive behavior. Use minmax for responsive floors: repeat(auto-fit, minmax(280px, 1fr)).
49 cta-buttons-clustered-in-hero medium Three-plus CTA buttons clustered in hero Three or more CTA-styled buttons within a single hero is decision paralysis - the AI's compromise when it can't choose the primary action. Pick one primary CTA. Demote the rest to text links or move them to a secondary surface. If you genuinely need three actions, restructure the hero into a comparison or a stepped layout so the primary is unambiguous.
50 display-table-for-layout medium display: table used for layout outside data tables display: table on a non- element is a 2008-era IE6 hack for vertical centering before flexbox existed.
Use flexbox or grid: display: grid; place-items: center; for the centering case, or display: flex; align-items: center for one-axis.
51 flex-center-center-default low flex + justify-center + align-center as the default block display: flex + justify-content: center + align-items: center wrapped around content is the AI's default-centering recipe - signals the layout was reached for in three lines without thinking about asymmetric balance, content priority, or whether the content actually wants to be centered. Use display: grid; place-items: center if you genuinely want full centering (one line, clearer intent).

Typography (10 rules — 52–61 of 100)

# ID Severity Name Why Fix
52 inter-as-display high Inter used as display font Inter is a body font tuned for screen legibility at small sizes; deployed as display it reads as the default startup-landing fingerprint. Pair Inter (body) with a distinctive display face: Geist, Satoshi, Cabinet Grotesk, General Sans, Outfit, or a brand-specific variable sans.
53 font-system-only low System font stack with no chosen typeface Falling back to the system stack alone with no chosen typeface gives up the chance to have any typographic identity. Pick a real face: Geist, Inter, Satoshi, IBM Plex Sans, Cabinet Grotesk, or the brand's variable.
54 hero-text-arbitrary-90px medium Arbitrary hero font size Arbitrary px values like text-[90px] or text-[112px] bypass the type scale and signal the size was picked by eye, not by system. Extend the Tailwind theme scale or use clamp() for fluid hero typography.
55 title-case-headlines medium Title Case On Display Headings Marketing-style Title Case on every headline dates the design to a 2014 SaaS template and slows reading. Default to sentence case for headlines.
56 display-bold-700 medium Display heading at 700+ font-weight Display fonts at 700 weight read dense and visually muddy. Use font-weight 500 or 600 on display headings.
57 all-caps-large medium ALL CAPS at body-text size or larger ALL CAPS reading speed is 13-20% slower than mixed case because word shape is lost. Reserve uppercase for eyebrows, labels, and badges under 14px.
58 letterspacing-tracking-tight-display medium Tight letter-spacing on heavy display heading tracking-tighter (-0.05em) on font-weight 800+ display is the unmistakable 'AI hero' typographic recipe - copied from every Linear-clone landing page. Use letter-spacing -0.01em to -0.02em on display headings, with font-weight 500-600.
59 text-3xl-4xl-5xl-stack medium Tailwind text-3xl text-4xl text-5xl AI hero stack text-3xl md:text-4xl lg:text-5xl (and the 4xl/5xl/6xl shift) is the verbatim Tailwind tutorial cadence for every AI-generated hero - signals the responsive type was set by stepping through preset utilities, not by tuning the actual feel at each breakpoint. Use clamp() for fluid type: text-[clamp(2.5rem,5vw,4.5rem)] or define a custom step in tailwind.config that jumps by content priority, not utility index.
60 font-family-monospace-fallback-default low font-family: monospace with no specific stack Bare font-family: monospace (no specific stack before the generic) hands rendering to Courier New on Windows and Monaco on old Macs - ugly defaults that signal nobody picked a code face. Specify the stack: font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;.
61 font-weight-numeric-100-or-900-on-body high Body or paragraph font-weight set to 100 or 900 Setting body or paragraph font-weight to 100 (Thin) makes long-form reading nearly impossible at any size below 24px - the strokes literally disappear on most displays. Body weight should be 400 (Regular) or 450-500 for a slightly stronger feel.

Color (9 rules — 62–70 of 100)

# ID Severity Name Why Fix
62 purple-to-blue-gradient high Default purple-to-blue AI gradient Purple-to-blue gradient on white is the strongest visual fingerprint of unconstrained model output and reads as template-marketplace AI slop. Use a single restrained accent (Emerald, Electric Blue, Deep Rose, Amber) against neutrals; keep gradient hue spread under 60 degrees if used at all.
63 gradient-text-rainbow medium Multi-stop gradient text Three-stop rainbow text on a hero word is the strongest 'AI hero' tell after the purple-to-blue gradient. Use a single solid color for headlines.
64 card-glow-purple-shadow medium Purple glow shadow on cards Purple-tinted shadows on cards are the second-strongest 'AI premium' fingerprint after the gradient itself. Use neutral diffusion shadow: shadow-[0_20px_40px_-15px_rgba(0,0,0,0.08)].
65 tailwind-color-named-vague low Named Tailwind colors with no semantic token Raw bg-blue-500 / bg-purple-500 with no semantic token (primary, success, danger) signals the color system was never designed. Define semantic tokens in tailwind.config or CSS custom properties: bg-primary, text-success, ring-accent.
66 gradient-mesh-purple-pink medium Purple-pink mesh gradient hero Purple-to-pink mesh gradient is the second-most fingerprinted AI-hero pattern after blue-to-purple - 'I generated a landing page' aesthetic. Single accent against neutral canvas.
67 dark-text-on-dark-card high Low-contrast text on card Dark text token (zinc-500/600/700) on a dark surface fails WCAG AA 4.5:1 contrast and signals the dark mode pairing was never tested. Test contrast on both themes. On dark surfaces use text-zinc-100 to text-zinc-300; reserve 500-700 for tertiary captions on light surfaces.
68 chrome-y-multi-stop-gradient high Chrome-y multi-stop gradient Three-plus color stops in a linear-gradient is the v0 / Cursor / generic-AI default; reads as chrome-y template output rather than a deliberate brand decision. Use a two-stop gradient (or a single solid) with hue spread under 60 degrees.
69 glass-without-fallback medium backdrop-filter blur without background fallback backdrop-filter is unsupported on Firefox and older Safari - without a translucent background fallback, the surface renders fully transparent and content underneath bleeds through. Always pair backdrop-filter: blur with a background-color (e.g.
70 gradient-on-text-rainbow high Rainbow gradient on display text background-clip: text on a multi-stop linear-gradient is the AI hero's chrome-y rainbow word - the loudest signal that no taste decision was made on color. Use one solid color on display text.

Visual (9 rules — 71–79 of 100)

# ID Severity Name Why Fix
71 blur-bg-only-decoration low Backdrop blur with no glass surface Backdrop blur applied without a translucent surface behind it is GPU work for zero visual benefit - decoration, not design. Pair backdrop-blur with a translucent fill: bg-white/70 or bg-zinc-950/60.
72 lone-emoji-as-icon high Lone emoji used as functional icon A bare emoji standing in for an icon renders inconsistently across platforms, ignores brand color, and is announced unpredictably by screen readers. Use inline SVG (Lucide, Feather, Phosphor, Heroicons) at 1.5-2px stroke with currentColor.
73 box-shadow-multilayer-default medium Five-plus box-shadow layers stacked Stacking five or more shadow layers is the AI's idea of premium depth; reads as visual bloat and chews the GPU on paint. Use one ambient diffusion shadow (e.g.
74 dynamic-island-glow-everywhere medium Dynamic Island inner glow on cards iOS Dynamic Island's inner-glow + blur recipe is being copied as the default on every card and pill - reads as the 2024 AI-marketing-template tell. If you want depth, use a single outer diffusion shadow tuned to the surface.
75 emoji-bullet-marker high Emoji at start of list item An emoji at the start of every list item is the AI signature - check marks, sparkles, rockets, party poppers used as bullet markers signal a generator that confused decoration with hierarchy. Use the native list disc, an SVG check icon, or a numeric counter.
76 noise-texture-overlay medium Repeating noise or grain texture overlay A noise.png or grain.svg tiled across a hero is the 2024 generative-UI fingerprint - signals 'I added texture' without a coherent material story. If grain is a deliberate brand choice, generate it via SVG with low baseFrequency and 4-6% opacity.
77 border-radius-2xl-default low rounded-2xl or larger applied to everything rounded-2xl/3xl (24-32px+) on every card, button, and input is the Tailwind AI signature - signals the radius was set once and copy-pasted instead of being calibrated per element size. Calibrate radius to element size: 4-8px for inputs, 8-12px for buttons, 12-16px for cards, 16-24px for marketing surfaces only.
78 glass-morphism-default medium Backdrop-blur frosted-glass on four-plus surfaces Frosted-glass everywhere is the iOS-aesthetic AI tell - signals the generator confused 'depth' with 'blur every surface'. Reserve glass for one or two surfaces that have a clear reason to feel translucent (a floating nav, a notification toast).
79 loader-spinner-border-default low Default CSS border-spin loader recipe The 4-border-color, border-top-different, animation: spin recipe is the textbook 1998 spinner that every AI generator emits for any loading state - signals the loading affordance was set by default, not designed. Replace with a skeleton screen for content loading, a progress indicator for known-duration tasks, or a branded SVG mark animated via transform: rotate.

Quality (9 rules — 80–88 of 100)

# ID Severity Name Why Fix
80 inline-style-attribute medium Inline style attribute Inline style= attributes bypass the design system, defeat caching, and signal the styling decision was made ad hoc. Use Tailwind utilities, a CSS class, or a styled component.
81 console-log-leak high console.log in component code console.log shipped to production leaks state to anyone with DevTools open and signals the build didn't go through a real review. Remove debug logs before shipping.
82 todo-fixme-comment low TODO or FIXME in shipping code TODO/FIXME in shipping code is an unresolved promise to the reader and a draft-state leak. Resolve the TODO before merge, or move it to the issue tracker with a specific owner and date.
83 any-type-leak medium TypeScript any type any defeats the type system and signals the type was never figured out - the code is JavaScript pretending to be TypeScript. Use unknown for genuinely unknown shapes and narrow with type guards.
84 shadcn-default-everywhere low Default shadcn token block unmodified The default shadcn HSL token block with --radius 0.5rem is recognizable in two seconds by anyone who has shipped one shadcn app. Customize tokens at init: swap slate for zinc/stone, shift primary to a brand hue, set --radius to 0.625rem or 0.75rem.
85 arbitrary-z-index-9999 medium Lazy z-index value z-index: 9999 is the 'I gave up on the stacking system' value - signals there's no z-scale token map and stacking bugs are coming. Define a z-scale token set (z-base: 0, z-dropdown: 10, z-sticky: 20, z-modal: 50, z-toast: 60).
86 numbered-placeholder-classname low Numbered placeholder class names (card-1, feature-2) card-1, feature-2, step-3 are the throwaway class names a generator emits when it has no semantic for what each block actually contains - signals nobody named the content. Name classes by purpose: .pricing-card, .feature-comparison, .onboarding-step.
87 padding-shorthand-4-vals-zero-first low Verbose 4-value padding/margin where 3 would do Writing padding: 0 16px 16px 16px when padding: 0 16px 16px collapses to the same result is the AI tell of a generator that copied a 4-value shorthand without simplifying. Use the shortest equivalent shorthand: 1 value (all sides), 2 values (vertical | horizontal), 3 values (top | horizontal | bottom).
88 class-multiple-utility-than-token low Five-plus utility classes where a token would do Stacking text-sm text-gray-500 font-medium leading-5 tracking-tight on a single element signals every type decision was made inline by a generator that never reached for a token. Extract recurring stacks to a named token: @apply text-sm text-gray-500 font-medium leading-5 tracking-tight on a .text-body-meta class, or define semantic font-utilities in tailwind.config.ts (text: { 'body-meta': [...] }).

Motion (8 rules — 89–96 of 100)

# ID Severity Name Why Fix
89 timing-300ms-default low Default 300ms transition timing 300ms is the editor default - using it everywhere signals zero motion intent and reads as laziness to anyone who tunes animation curves. Micro-interactions 150-220ms, complex transitions 250-400ms, exit 60-70% of entry.
90 cubic-bezier-material-only low Material default easing everywhere cubic-bezier(0.4, 0, 0.2, 1) is Material Design's default standard ease - using it as the only curve signals no taste decision was made. Use cubic-bezier(0.16, 1, 0.3, 1) for premium exits, cubic-bezier(0.34, 1.56, 0.64, 1) for playful overshoot, or spring physics.
91 cta-arrow-rightward-bouncing medium Bouncing arrow on CTA Bouncing arrow on a CTA is the desperate-attention pattern that signals the copy itself isn't doing the work. Let the CTA copy carry the action (Start a 14-day trial, Read the deployment guide).
92 animating-layout-properties high Transition on a layout property Animating width, height, top, left, margin, padding triggers layout and paint on every frame - the dropped-frame jank pattern that makes the UI feel cheap. Animate transform (translate, scale) and opacity only.
93 scale-1-1-on-card-hover low Over-the-top hover scale scale(1.1) or higher on hover reads as amateur - it shouts when a 1-2 percent nudge or a shadow lift would be more confident. Use scale(1.01) to scale(1.03), or replace the scale with a translateY(-2px) plus shadow shift.
94 transition-property-all medium transition: all (lazy property list) transition: all is the lazy default that animates every property change - including layout-triggering ones like width/height/margin - causing jank and unintended motion. List the exact properties: transition: transform 200ms ease, opacity 200ms ease.
95 animation-duration-too-long medium Micro-interaction with animation-duration over 800ms A hover, focus, or state-change animation longer than 800ms feels sluggish - users perceive UI under 200ms as instant, under 500ms as snappy, over 800ms as broken or laggy. Cap micro-interactions at 150-400ms.
96 transition-duration-500ms-or-longer medium Interactive transition-duration 500ms or longer A transition-duration of 500ms or more on hover, focus, or active states feels laggy - the human perception threshold for 'instant' is 100ms and for 'snappy' is 200ms. Cap interactive transitions at 150-300ms.

Performance (4 rules — 97–100 of 100)

# ID Severity Name Why Fix
97 img-no-dimensions high Image without width and height attributes An without width and height attributes causes Cumulative Layout Shift as the image loads and the page reflows - the single biggest CLS regression on most sites. Always set width and height (the intrinsic pixel dimensions) on every , even when styled with CSS.
98 animated-grain-noise-no-perf-class low Animated grain or noise without GPU hint Animated grain or noise without a will-change or transform hint forces a CPU repaint every frame and causes paint thrashing on low-end devices. Add will-change: transform or compose the noise via an SVG overlay on a transformed layer.
99 event-listener-no-passive-on-scroll medium addEventListener('scroll' | 'touchmove' | 'wheel') without passive scroll, touchmove, touchstart, and wheel listeners default to passive: false, which means the browser must wait for the handler to finish before scrolling - any sync work blocks the scroll thread and creates the 'janky scroll on a fancy page' feel. Pass { passive: true } as the third argument: el.addEventListener('scroll', fn, { passive: true }).
100 image-format-jpg-no-webp-avif medium without source AVIF/WebP A raw with a .jpg or .png source ships 3-10x more bytes than the same image as AVIF or WebP. Wrap in with AVIF first, WebP next, JPG fallback: ....

Adding a rule

The full JSON shape and the steps to add a 101st rule live in Linter Rules — Adding a new rule.

The discipline: every rule needs a tight why (one sentence on the AI-slop fingerprint, the WCAG number, or the CWV impact) and a concrete fix (one sentence with a token, a property, or a pattern named explicitly). Vague rules don't ship.


See also: Linter Rules · Recommender Engine · Architecture · Anti-AI slop ban list Source: data/anti-patterns.json Linter: engine/linter/core.py

Clone this wiki locally