# Anti-Pattern Catalog

Use during Discover (baseline audit), Design (prevention), and Verify (compliance scoring).

---

## The AI-Slop Test

Before shipping any artifact: **"If I told someone 'AI made this,' would they believe me immediately?"**

If yes - redo. The training-set monoculture has specific fingerprints. Learn them all.

---

## Hard Bans (BAN-XX)

Zero tolerance. Each violation = −3 points from Anti-Pattern score. Rewrite rather than soften.

### BAN-01: Side-Stripe Borders

bdId: BAN-01

```css
/* BANNED — AI-generated card tell, regardless of color */
border-left: 4px solid var(--color-primary);
border-right: 3px solid #6366f1;
```

**Why**: The border-left accent card is the most recognizable AI default. It signals no design thinking happened.

**Fix**: Use different element structure - colored icon, colored background section, or inline accent element.

**Grep**: `border-left:\s*[2-9][0-9]*px|border-right:\s*[2-9][0-9]*px`

### BAN-02: Gradient Text

bdId: BAN-02

```css
/* BANNED */
background: linear-gradient(to right, #6366f1, #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
```

**Why**: Every AI-generated hero uses this. It's a visual cliché and fails against light backgrounds.

**Fix**: Solid color, emphasis via weight or size increase.

**Grep**: `background-clip:\s*text|text-fill-color:\s*transparent`

### BAN-03: Bounce/Elastic Easing

bdId: BAN-03

```css
/* BANNED */
transition: all 300ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
animation: bounce 1s ease;
animation-timing-function: spring(1, 80, 10, 0);
```

**Why**: Skeuomorphic residue from 2012. Feels toy-like and unprofessional.

**Fix**: `ease-out` for enter, `ease-in` for exit, `ease-in-out` for transitions.

**Grep**: `cubic-bezier\(.*-[0-9]|bounce|elastic|spring\(`

### BAN-04: Animating Keyboard Actions

bdId: BAN-04

No animation on: command palette open/close, keyboard shortcuts, tab switching, filter/sort toggles, navigation item expand/collapse.

**Why**: These repeat 100+ times per day. Every millisecond of animation accumulates into felt sluggishness.

**Fix**: Instant state change. Zero transition duration.

### BAN-05: Pure Black Dark Mode

bdId: BAN-05

```css
/* BANNED */
background: #000000;
background: rgb(0, 0, 0);
```

**Why**: Pure black in dark mode creates harsh contrast and looks amateur.

**Fix**: oklch(12% 0 0) through oklch(18% 0.005 [hue]) - slightly warm or cool near-black.

**Grep**: `background.*#000000|background.*rgb\(0,\s*0,\s*0\)`

### BAN-06: Disabling Zoom

bdId: BAN-06

```html
<!-- BANNED -->
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="viewport" content="width=device-width, maximum-scale=1">
```

**Why**: WCAG 1.4.4 violation. Breaks accessibility for low-vision users.

**Grep**: `user-scalable=no|maximum-scale=1`

### BAN-07: Naked `outline: none`

bdId: BAN-07

```css
/* BANNED — unless a custom focus indicator replaces it */
:focus { outline: none; }
button:focus { outline: 0; }
```

**Why**: Removes keyboard navigation visibility. WCAG 2.4.7 failure.

**Fix**: `:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }` with `outline: none` only on `:focus:not(:focus-visible)`.

**Grep**: `:focus\s*\{[^}]*outline:\s*(none|0)`

### BAN-08: `transition: all`

bdId: BAN-08

```css
/* BANNED */
transition: all 300ms ease;
```

**Why**: Animates every property including layout properties (width, height, top) causing expensive paint/layout operations.

**Fix**: Specify exact properties: `transition: transform 200ms ease-out, opacity 150ms ease-out;`

**Grep**: `transition:\s*all\s`

### BAN-09: `scale(0)` Animation Entry

bdId: BAN-09

```css
/* BANNED — nothing in the real world materializes from nothing */
@keyframes enter { from { transform: scale(0); } }
```

**Fix**: `scale(0.95)` + `opacity: 0` → `scale(1)` + `opacity: 1`.

---

## AI-Slop Tells (SLOP-XX)

Each confirmed tell = −1 point from Anti-Pattern score. These signal training-set monoculture without deliberate design choices.

### SLOP-01: AI Default Palette

```
#6366f1 (indigo-500) + #8b5cf6 (violet-500) + #06b6d4 (cyan-500)
```

The exact palette generated by GPT-4 and Claude for 80% of UI requests.

**Grep**: `#6366f1|#8b5cf6|#06b6d4`

### SLOP-02: Purple → Blue Gradient Hero

```css
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(to right, #6366f1, #8b5cf6);
```

### SLOP-03: Cyan Accent on Dark Background

`color: #06b6d4` or `color: #22d3ee` as the primary accent in dark mode.

### SLOP-04: `backdrop-filter: blur` Without Purpose

```css
/* SLOP if used purely decoratively */
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(10px);
```

Glassmorphism as the default card treatment signals no layout thinking.

**Valid uses**: Modal overlays dimming content behind them, floating command palette, persistent header over content.

**Grep**: `backdrop-filter:\s*blur`

### SLOP-05: Default Font Without Decision

Using Inter, DM Sans, Space Grotesk, or Plus Jakarta Sans without a documented brand reason.

**Test**: Can you give 3 concrete reasons this font matches the product's brand? If not - choose again.

### SLOP-06: Card-in-Card

A component with `border-radius` + `background` + `box-shadow` nested inside another such component.

### SLOP-07: "Icon in Rounded Square Above Every Heading"

The feature section pattern where every item has: `rounded-xl bg-primary/10 p-3 mb-4` containing an icon, followed by a heading and 2 sentences.

### SLOP-08: Rainbow Status Badges

Assigning a different color to every status: blue (pending), yellow (in review), purple (waiting), orange (blocked), teal (processing). Status color must be semantic: green (success/active), yellow (warning/pending), red (error/blocked), gray (inactive/draft). Only those four roles.

### SLOP-09: Decorative Sparklines

Charts placed in dashboards with no real data, purely decorative "trend lines" that don't encode information.

### SLOP-10: Generic Drop Shadow

```css
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
```

The Tailwind `shadow-md` default. Used without considering elevation system.

### SLOP-11: "Simple, Powerful, Flexible" Copy

Hero headline triplets with no subject or verb. Feature list as the hero. "We're passionate about..." opener.

### SLOP-12: 3D Isometric Illustration

Pastel-colored isometric illustrations with floating icons. Figma community template vibe. Use photography, real screenshots, or purposefully-styled illustration with brand character.

---

## Visual Anti-Patterns

### Color Errors

- Gray on colored backgrounds (fails contrast, not "subtle")
- Pure gray neutrals - add 0.005–0.015 chroma toward brand hue
- Red + green as the only meaning carrier (colorblind failure)
- Text over image without scrim or overlay
- Alpha-heavy transparency everywhere (incomplete palette signal)
- > 1 semantic role for the same color (red = danger ONLY)

### Typography Errors

- Body text < 16px on web (< 12px anywhere)
- > 6 font-size/weight combinations on a single page
- `all-caps` body text (labels/badges only)
- Fluid `clamp()` on app/dashboard UI (marketing headings only)
- Line height 1.0–1.2 on body text (needs 1.5–1.75)
- `letter-spacing` changes with no typographic reason
- `font-weight: 300` on text < 16px (illegible)

### Layout Errors

- Full-width buttons for every CTA (primary only)
- Placeholder-only labels (no visible `<label>` above input)
- Multi-column forms on mobile
- Hamburger as PRIMARY nav on desktop (> 1024px)
- Bottom nav with > 5 items
- Container width fixed at 1200px (breaks at 1300+)
- Spacing values not from 4/8/12/16/24/32/48/64 series (e.g. `padding: 13px`)

### Shadow/Elevation Errors

- 3+ shadow depths when 2 tiers is enough
- Shadow AND border AND background color on the same element
- Inner shadow + outer shadow simultaneously
- Drop shadows on flat/minimal design systems

---

## Motion Anti-Patterns

- `ease-in` on entrances (use `ease-out`)
- Same duration for enter and exit (exit should be 60–70% of enter)
- Animating `width`/`height` (triggers layout - use `transform` only)
- No `prefers-reduced-motion` media query
- Popover origin `transform-origin: center` (should come from trigger element)
- Button with no `:active` press feedback
- Toast re-playing entrance on every state update
- Decorative pulses > 1s that loop forever

---

## UX/Interaction Anti-Patterns

### Forms
- Validation firing on every keystroke (exception: password strength)
- Errors shown only at top of form for field-level problems
- Native `<select>` for > 10 options without typeahead search
- No progressive disclosure on long forms (show all fields upfront)

### Feedback
- Instant (0ms) state transitions - feels broken
- "Loading..." spinners with no context ("Loading contacts..." is better)
- Success toast that blocks the next interaction
- Destructive action with no undo and no confirmation
- Confirmation dialog for reversible action (annoyance - use undo instead)

### Navigation
- Browser back button breaks app state
- Custom back button overriding native swipe gesture (mobile)
- FAB (floating action button) on iOS native (Material Design pattern, not HIG)

---

## Copy Anti-Patterns

- Placeholder text as the only label (disappears on focus; accessibility failure)
- "Click here" / "Read more" / "Learn more" links (use verb-first + specific: "Download the guide")
- "OK" / "Submit" / "Yes" buttons (use action verbs: "Save changes", "Create account", "Delete message")
- Error messages that blame the user: "You entered an invalid email" → "Email address not recognized"
- "Oops!" / "Uh oh!" in error states
- Empty state that only says "No items found" (missed onboarding opportunity)

---

## Auto-Detection Grep Commands

Run these against the codebase for quick anti-pattern audit:

```bash
# BAN violations (−3 each)
grep -rn "border-left:\s*[2-9]" src/ --include="*.css" --include="*.scss"
grep -rn "background-clip:\s*text" src/
grep -rn "text-fill-color:\s*transparent" src/
grep -rn "cubic-bezier(.*-[0-9]" src/
grep -rn "user-scalable=no\|maximum-scale=1" public/
grep -rn ":focus\s*{" src/ | grep -v "focus-visible"
grep -rn "transition:\s*all\s" src/

# SLOP signals (−1 each)
grep -rn "#6366f1\|#8b5cf6\|#06b6d4" src/
grep -rn "backdrop-filter:\s*blur" src/
grep -rn "bounce\|elastic" src/ --include="*.css"
```

---

## Pre-Ship Self-Check

- [ ] Default font (Inter, DM Sans, Space Grotesk) used without a brand reason?
- [ ] Accent uses purple → blue gradient, cyan-on-dark, or AI default palette?
- [ ] Any card-in-card nesting?
- [ ] Every shadow a rounded-rectangle drop shadow?
- [ ] Hero headline: "Simple, powerful, [noun]"?
- [ ] Decorative sparklines with no real data?
- [ ] Empty state just says "No items found"?
- [ ] Border-left accent on cards/alerts?
- [ ] Gradient text on any heading?
- [ ] Bounce or elastic easing anywhere?

If YES to any → rewrite that element before proceeding.

---

### BAN-10: Same Border-Radius on Nested Surfaces

bdId: BAN-10

Applying the same `border-radius` to a container and an element inside it (when the element is separated by padding) makes the inner element appear to "float" - the radii should be concentric, not equal.

**Grep (Tailwind):**
```
(rounded-\w+)[^"]*"[^>]*>\s*<[^>]*\1
```
**Grep (CSS):** Look for identical `border-radius` values in parent and child selectors within the same component.

**Fix:** Apply the concentric formula: `innerRadius = outerRadius − padding`. See `reference/surfaces.md`.

Source: jakubkrehel/make-interfaces-feel-better (MIT)

---

### BAN-11: Tinted Image Outline

bdId: BAN-11

Using a colored outline on images (e.g., `outline-slate-200`, `outline-gray-300`, or a hex-value outline color) competes visually with the image content and creates color contamination.

**Grep (Tailwind):**
```
outline-(slate|zinc|neutral|gray|stone|blue|red|green|yellow|purple)-\d+
```
applied to `<img>` elements.

**Grep (CSS):**
```
img\s*\{[^}]*outline:\s*[^}]*#[0-9a-fA-F]{3,8}
```

**Fix:** Use `outline: 1px solid rgba(0,0,0,0.08)` (light) or `outline: 1px solid rgba(255,255,255,0.08)` (dark). Pure black or white at low opacity only. See `reference/surfaces.md`.

Source: jakubkrehel/make-interfaces-feel-better (MIT)

---

### BAN-12: `transition: all`

bdId: BAN-12

`transition: all` animates every animatable CSS property on the element, including layout-triggering properties (width, height, padding, margin). This causes layout recalculation on EVERY transition, creating jank and unexpected visual effects (e.g., a hover transition that also animates the element's size if any dimensions change).

**Grep (CSS):**
```
transition:\s*all
transition-property:\s*all
```
**Grep (Tailwind):** bare `\btransition\b` class (without a modifier like `transition-transform` or `transition-[specific-property]`).

**Fix:** Specify the exact properties to animate. For hover effects: `transition: background-color 150ms, color 150ms, opacity 150ms`. For motion: `transition: transform 200ms, opacity 200ms`.

Source: jakubkrehel/make-interfaces-feel-better (MIT)

---

### BAN-13: `will-change: all`

bdId: BAN-13

`will-change: all` promotes every animatable property to its own GPU compositor layer, consuming GPU memory for each property. On complex components this can allocate hundreds of MB of texture memory per instance, causing performance degradation and potential crashes on mobile.

**Grep:**
```
will-change:\s*all
```

**Fix:** Restrict to specific compositing-safe properties: `will-change: transform`, `will-change: opacity`, `will-change: filter`, or `will-change: clip-path`. Remove entirely after animation completes. See `reference/motion.md` will-change section.

Source: jakubkrehel/make-interfaces-feel-better (MIT)
