Content
Copy and layout choices that make UI read as generated rather than designed. These rules apply to every build target: real React components and the framework-free HTML kit alike.
Content & Cognitive Load
Walls of text and unread content
Rule: Do not ship more copy, options, or UI than a person will actually read. Every block of text, list item, and control spends the reader's finite attention (processing fluency). Cut to the fewest words that carry the meaning; default to subtraction over addition.
// ❌ Bad (three sentences where one works; nothing scannable)
<p>
Welcome to your dashboard. This is the place where you can see all of the
different metrics and data points that we have collected for you over time,
and you can also use the controls below to filter, sort, and customize
exactly how all of that information is displayed on your screen.
</p>
// ✅ Good (one line, the rest revealed on demand)
<p className="text-muted-foreground">Your metrics at a glance.</p>
<button>Customize view</button>
Why it matters: AI makes it trivial to generate walls of text and long option lists. Unread content is not neutral, it raises cognitive load, buries the primary action, and makes the product feel heavier. Removing is harder than adding, so it gets skipped; flag copy and choices that do not earn their place. See ux/cognitive-laws.md (Processing Fluency, Cognitive Load) and ux/microcopy.md.
Severity: Moderate
Overwritten UI copy
Rule: UI copy is part of the design. Headlines carry at most about 6 words; a supporting line carries about 15 and each section gets at most one. Cut filler adjectives (seamless, effortless, powerful, beautiful), exclamation marks, and restated headings. Never use emoji as icons or status indicators: use the design system's icon library or status badge variants.
The remaining copy must make the user benefit and next outcome concrete. CTAs use specific action verbs; predictable doubts are reassured beside the decision; duplicate labels and sentences are removed. Remove the brand and imagery as a swap test: if a competitor could reuse the words unchanged, rewrite them from the user's actual problem and vocabulary.
// ❌ Bad (filler, emoji icon, restates itself)
<h2>Powerful, Seamless Match Predictions! ⚽</h2>
<p>Our powerful prediction engine lets you seamlessly predict matches with your friends in a fun and effortless way.</p>
// ✅ Good (specific, one line)
<h2>Predict matches</h2>
<p className="text-muted-foreground">Stake coins on tonight's fixtures.</p>
Why it matters: Filler copy and emoji icons are the fastest generated-content tells. Specific, short copy reads as designed and keeps the primary action visible.
Severity: Moderate
Ceremonial copy with no user job
Rule: Every visible text fragment must earn one concrete user-facing job. It must do at least one:
- Identify context or origin when it is unclear.
- Explain what happened.
- Make the next action clear.
- Reduce a real user doubt.
- Satisfy a legal or security requirement.
Do not add provenance lines, helper captions, slogans, onboarding prose, or footer copy just because a familiar template usually includes them. Run the deletion test: remove the line. If meaning, action clarity, safety/trust, legal compliance, and intentional tone do not get worse, keep it removed. Provenance, legal, and security copy are valid only when they change what the user understands or can safely do.
// ❌ Bad: repeats the sender without helping the user
<AuthEmailFooter>This link was sent by Example Co.</AuthEmailFooter>
// ✅ Good: keeps only the line that reduces a real doubt
<p>If you did not request this, you can ignore this email.</p>
Why it matters: Generated UI often completes familiar templates instead of designing the moment. Self-evident copy is not neutral: it creates visual noise, slows scanning, and makes the artifact feel assembled from defaults rather than edited for the user.
Severity: Moderate
Interchangeable value proposition
Rule: A page that sells or explains a product must say who it is for and what makes it different, in the headline and the supporting line. Paste a competitor's name over the headline: if it still reads true, the page has no positioning and the copy is decoration. Fix it upstream, not by rewording. Name the person the product is for, the alternative they use today, and the one difference that would make them switch. If those answers are unknown, ask the user for them and say the positioning is missing. Never fill the gap with adjectives, and never invent a customer, a quote, a count, or an award (see "Fabricated social proof and activity").
// ❌ Bad (any competitor could run this unchanged)
<h1>Build better products, faster</h1>
<p>The all-in-one platform for modern teams.</p>
// ✅ Good (names the person, the alternative, and the difference)
<h1>Invoicing that takes a minute, not an afternoon</h1>
<p className="text-muted-foreground">For freelance editors who bill by the project and track it in a spreadsheet today.</p>
Why it matters: Launching is cheap now, so attention is the scarce resource. A headline that describes a category instead of a difference is skipped, however well the page is built. Positioning is the one part of the page that cannot be generated, because it is a fact about a real business.
Severity: Serious
Points trailing out of winding prose
Rule: When copy makes more than one point (benefits, reasons, steps, options), summarize it into a framework: name the structure first (the count and the frame), then fill it in, in that order. Do not let points trail out of a run-on sentence. The frame is a contract: a "3 things" opener followed by four points, or points in a different order, breaks it. If a point does not fit the frame, change the frame or cut the point; never bolt it on as an afterthought.
// ❌ Bad (points trail out of one winding sentence)
<p>This plan is great because it gives you unlimited projects and it also lets your team collaborate and you get priority support too.</p>
// ✅ Good (frame first, then exactly those items in order)
<p>Three things Pro unlocks:</p>
<ul>
<li>Unlimited projects</li>
<li>Team collaboration</li>
<li>Priority support</li>
</ul>
Why it matters: A named frame ("3 steps", "2 options") primes the reader and makes the copy scannable at a glance; trailing prose forces the reader to reconstruct the structure themselves. See ux/microcopy.md (The Framework Principle).
Severity: Moderate
Reward detached from user value
Rule: At meaningful confirmation, completion, security, payment, progress, or milestone moments, feedback must first confirm what happened and then surface a proportional user payoff. Choose the emotional job deliberately: control for uncertainty, competence for effort and progress, or recognition for a genuinely social accomplishment. Do not celebrate anxious states, bury a real payoff below receipts or secondary chrome, or use confetti and oversized animation for tiny repeated actions. Test whether the treatment remains useful on the tenth repetition.
// ❌ Bad: generic celebration, no system status or user value
<Success title="Amazing!" confetti>Done</Success>
// ✅ Good: confirms the outcome and makes the contextual payoff concrete
<Success title="Order confirmed" detail="Arrives between 14:00 and 15:00" />
Why it matters: Delight is not decoration. A mismatched reward feels manipulative or noisy; a concrete, contextual payoff builds trust, makes progress visible, and reinforces behavior that benefits the user.
Severity: Moderate
Line breaks preserve complete phrases
Rule: Review headings, subtitles, card titles, and short display copy in the rendered interface at every target width. Line breaks must follow the meaning of the sentence, not whichever word happens to reach the container edge. Prefer breaks between sentences or clauses. Keep short noun phrases ("product quality"), names, numbers with units, and other words that are understood together on the same line. Avoid orphaned final words and lines that end with an article, conjunction, or preposition when a small copy, width, or type adjustment can prevent it.
For headings and short display text, start with text-wrap: balance, then adjust the text measure or font size if the browser still produces an awkward break. Use intentional block spans or <br> only for stable editorial compositions such as presentation slides, and test those breaks at every supported viewport. For paragraphs and responsive body copy, do not insert manual line breaks: constrain the measure to roughly 45–75 characters and use text-wrap: pretty so the text can reflow naturally. Recheck long localized strings rather than relying on nonbreaking spaces that can cause overflow.
// ❌ Bad: the rendered heading breaks phrases by accident
// AI can generate
// code. It cannot
// generate product
// quality.
<h1 className="max-w-[11ch]">AI can generate code. It cannot generate product quality.</h1>
// ✅ Good: fixed presentation copy uses deliberate semantic lines
<h1>
<span className="block">AI can generate code.</span>
<span className="block">It cannot generate product quality.</span>
</h1>
// ✅ Good: responsive web copy keeps control of its own reflow
<h1 className="max-w-[22ch] text-balance">Build interfaces people can trust.</h1>
<p className="max-w-[65ch] text-pretty">Supporting copy reflows without editorial line breaks.</p>
Why it matters: Readers process text in phrases, not isolated words. A break inside a short semantic unit adds avoidable reconstruction work, weakens the message, and makes a polished slide or page feel accidental. The problem is most visible in large display type, where every line becomes part of the composition.
Severity: Serious
Microcopy details
Rule: The small strings get the same care as the headlines. Pluralize counts ("1 bet", "2 bets", never "1 bets"). No redundant self-labels ("You (you)"). Spell out abbreviations a new user cannot expand ("Matchday 3", not "MD3"). Units and currency use real words or an inline icon, never an improvised glyph ("1,000 coins", not "§1,000"). Empty states name the next action ("Pick a match to place your first bet"), not just zeros. A status badge repeated identically on every item in a list is noise; show it only when it differentiates.
// ❌ Bad
<span>1 bets placed</span> <strong>You (you)</strong> <span>§1,000</span>
// ✅ Good
<span>{n === 1 ? "1 bet placed" : `${n} bets placed`}</span> <strong>You</strong> <span>1,000 coins</span>
Why it matters: These details are where generated UI leaks through last. Each one is trivial alone; together they mark the difference between shipped and drafted.
Severity: Moderate
Contradictory displayed state and ranking
Rule: Keep one source of truth for mutable domain data and derive every balance, rank, total, return, counter, and success message from it. Exercise the main interaction through enough repetitions to cross a boundary or change an ordering, then verify that every displayed value describes the same state. Sort ranked lists by the value the rank represents. If an action changes the sort key, re-sort and re-render the full list immediately; never patch one DOM label while leaving peer values stale. Displayed balances, totals, returns, and success messages must agree after an action; never leave hard-coded sample data that contradicts the updated UI.
// ❌ Bad: the lower balance is ranked above the higher balance
<li>2. Marvin, 900 coins</li>
<li>3. Lucas, 920 coins</li>
// ✅ Good: rank and displayed balance agree
<li>2. Lucas, 920 coins</li>
<li>3. Marvin, 900 coins</li>
Why it matters: A polished surface becomes untrustworthy as soon as its numbers disagree. Generated demos often update one label while leaving related sample values untouched, so the completed interaction must be checked as a coherent state rather than as isolated components.
Severity: Serious
Generated-layout tells
Left-border accent callout boxes
Rule: Do not build callouts, notes, tips, or "key finding" boxes as a tinted panel with a colored vertical bar down the left edge (border-left: 3px solid <accent> plus a faint tinted background). This admonition pattern is a generated-content tell: it reads as a docs-engine artifact, not a designed component. Use a real card instead (border on all four sides, the surface's own radius and shadow), or plain inline emphasis. If the callout needs a category, use a small label or chip inside the card, not a side stripe.
// ❌ Bad (left-bar admonition)
<div className="border-l-4 border-blue-500 bg-blue-50 p-4">
<b>Key finding.</b> ...
</div>
// ✅ Good (a real card)
<div className="rounded-2xl border border-border bg-card p-5 shadow-sm">
<p className="text-xs font-semibold uppercase text-muted-foreground">Key finding</p>
<p>...</p>
</div>
Why it matters: The left-stripe callout is one of the most overused auto-generated UI shapes; shipping it makes a product look templated. A bordered card carries the same grouping with the design system's own surface treatment, so the callout belongs to the UI instead of fighting it.
Severity: Moderate
Dot-joined meta lines
Rule: Do not join facts with a middot (·). A line like Ari from Mono · Waiting 18 min chains unrelated facts at one weight, so the reader gets no help deciding which one matters. Give the line a hierarchy instead: the fact a person acts on carries the weight, and the rest recedes by size or color. Where two facts genuinely belong side by side, separate them with a comma, or place them in their own columns.
// ❌ Bad (two facts, one weight, joined by a dot)
<p className="text-sm text-muted-foreground">Ari from Mono · Waiting 18 min</p>
// ✅ Good (the wait time is the reason to act, so it leads)
<p className="text-sm font-medium text-foreground">Waiting 18 min</p>
<p className="text-xs text-muted-foreground">Ari from Mono</p>
Why it matters: The dot is the default separator of generated interfaces because it needs no decision about which fact matters. That is exactly the decision a designed row makes. A metadata line built this way scans as one grey string, so a user reading a list of them cannot triage without reading every word.
Severity: Serious
Labels above titles
Rule: Never place an eyebrow, kicker, category, or status label immediately above an h1, h2, or h3. Start with the title. If the context is essential, fold it into the title, put it in the single supporting line, or move it after the title.
// ❌ Bad (redundant label before the title)
<p className="text-xs uppercase tracking-widest">Resources</p>
<h1>Guides for better interfaces</h1>
// ✅ Good (the title leads)
<h1>Resources</h1>
<p>Guides for better interfaces.</p>
Why it matters: Labels above titles weaken hierarchy, duplicate information, and are a common generated-interface tell. A clear title should establish the section without a pre-title caption.
Severity: Moderate
Letter-spaced capital labels
Rule: Do not set labels as tiny spaced capitals. This covers text-xs uppercase tracking-widest and every variant of it, with or without font-mono, anywhere it appears: section labels, stat and KPI captions, table column headers, card headers, nav group headings, badges, and tab labels. The eyebrow-pill rule above bans one position; this rule bans the treatment everywhere. Use sentence case at the design system's own label size and muted color. Keep font-mono for code, IDs, keyboard shortcuts, and figures that must align in a column.
// ❌ Bad (the tell, repeated down a dashboard)
<p className="text-xs font-mono uppercase tracking-widest text-muted-foreground">Total revenue</p>
<h3 className="text-[10px] uppercase tracking-[0.2em]">Recent activity</h3>
// ✅ Good (the DS's own label treatment)
<p className="text-sm text-muted-foreground">Total revenue</p>
<h3 className="text-sm font-medium">Recent activity</h3>
Why it matters: Uppercase removes the ascender and descender shapes readers use to recognize words, so it is slowest to read at exactly the size where reading is hardest. Generated interfaces reach for it as a substitute for real hierarchy: when type scale, weight, and color are doing their job, the label does not need shouting to read as a label. Repeated across a screen it is the single most recognizable generated-UI signature.
A design system whose source genuinely uses spaced capitals is the exception, and only inside that system's own scoped components. .better-design/eslint-design-system.mjs flags the static-className case in feature code.
Severity: Moderate
Template hero and layout defaults
Rule: Do not default to the generated-landing-page template: a gradient hero with an uppercase eyebrow pill, a centered three-card feature grid, and glassmorphism panels over a radial-glow background. Reach for these shapes only when the brand or the user's request specifically calls for them. Default to the design system's own surfaces: plain background, real cards, the system's shadow and radius scale.
// ❌ Bad (the template stack)
<section className="bg-gradient-to-br from-green-600 to-blue-600">
<span className="rounded-full border px-3 py-1 text-xs uppercase tracking-widest">World Cup league</span>
<h1>Predict. Compete. Win.</h1>
</section>
// ✅ Good (the system's own surfaces)
<section className="bg-background">
<h1>Predict matches with your friends</h1>
<Button>Create a group</Button>
</section>
Why it matters: These shapes appear in nearly every generated landing page, so shipping them marks the product as templated even when the tokens are on-brand. The design system's own surfaces carry the identity without the tells.
Severity: Moderate
Cards, pills, and icons used as decoration
Rule: Do not wrap a single paragraph, a lone stat, or a page's only content in a card, and do not nest a card inside a card. Do not attach an icon to every list row, nav item, heading, and feature. Do not turn a plain noun into a pill or badge when it is not a status, a filter, or a count. Stretched empty panels and container overload governs which surfaces deserve a card at all and how they should size; this rule covers the containers and glyphs added purely as decoration.
// ❌ Bad (a container and an icon per line, none of them grouping or naming anything)
<div className="rounded-2xl border p-6">
<div className="rounded-xl border p-4">
<SparkleIcon /> <span className="rounded-full border px-2 py-0.5 text-xs">Fast</span>
<p>Your report is ready.</p>
</div>
</div>
// ✅ Good (plain text, one container only if it groups)
<p>Your report is ready.</p>
Why it matters: Borders, rounded corners, and small glyphs are the cheapest way to make a screen look considered without deciding what belongs together, so they accumulate wherever the layout was generated rather than designed. Each one costs contrast: when every line has an icon, the icons stop distinguishing anything, and when everything is boxed, the boxes stop grouping. Removing the container usually improves the screen, which is the test.
Severity: Moderate
Violet as the default accent
Rule: Take the accent from the project's own tokens. Do not reach for violet or indigo unless the brand actually uses it, and do not apply a gradient to a headline, a primary button, or an icon as a substitute for an accent decision. Template hero and layout defaults bans the gradient hero section; this rule covers the hue itself and gradients used as an accent anywhere else on the page.
// ❌ Bad (the default generated accent)
<h1 className="bg-gradient-to-r from-violet-500 to-fuchsia-500 bg-clip-text text-transparent">
Ship faster
</h1>
// ✅ Good (the project's accent, applied flat)
<h1 className="text-foreground">Ship faster</h1>
<Button>Start a project</Button>
Why it matters: This one hue and its gradient are the most recognizable signature of generated interfaces, so shipping it reads as a stock template even when the rest of the page is on-brand. Gradient text also costs contrast and defeats the token system that keeps a product's color consistent.
Severity: Moderate
Dark mode nobody asked for
Rule: Ship dark mode when the brand or the request calls for it, not as a default extra. Do not add a theme toggle to a product that did not ask for one, and do not build a screen dark because dark reads as technical. When the product does support both themes, both must be complete: every surface, border, and state defined in tokens, which the Dark mode inconsistencies rule covers.
// ❌ Bad (a toggle and a second theme nobody asked for, half defined)
<ThemeToggle />
<section className="bg-zinc-950 text-zinc-100">…</section>
// ✅ Good (one theme, taken from the project's tokens)
<section className="bg-background text-foreground">…</section>
Why it matters: An unrequested theme doubles the surface area that has to be designed, reviewed, and kept correct, and it is nearly always half finished, which is worse for the user than a single well-made theme. It also hides token mistakes: a screen that looks acceptable dark can be unreadable light.
Severity: Moderate
Prose tells
The rules above catch generated interface strings. These catch generated prose: the paragraphs in a hero, an about section, a pricing explainer, a changelog entry, a docs page, an empty-state paragraph. Prose is where the model's default voice leaks through hardest, because there is no label or button length to constrain it.
Apply these to any block of running text the product ships. Do not apply them to a user's own writing quoted inside the product, and do not rewrite a real quotation to fit them.
Banned words
Rule: These words never appear in shipped copy: delve, foster, leverage, utilize, facilitate, empower, streamline, robust, cutting-edge, paradigm shift, game changer, tapestry, realm, beacon, multifaceted, meticulous, intricate, paramount, transformative, elevate, embark, supercharge, harness, ever-evolving. Add the interface-copy fillers already banned above: seamless, effortless, powerful, beautiful.
Cut these phrases when they delay the point: it's worth noting, it's important to note, at the end of the day, when it comes to, at its core, in today's world, in the age of, the reality is, the truth is, in terms of, in order to, going forward, let's dive in.
Cut these adverbs when they add nothing: just, literally, honestly, simply, actually, truly, fundamentally, importantly, crucially, inherently, inevitably. Keep one when it carries real emphasis, contrast, or uncertainty.
// ❌ Bad
<p>Our robust platform empowers teams to streamline their workflow and leverage cutting-edge tooling.</p>
// ✅ Good
<p>Ship a design system in an afternoon. Your team reviews the live components in the browser.</p>
Why it matters: This vocabulary is the single most recognizable generated-writing signal. A reader who hits two of these words in one paragraph stops reading the claim and starts reading the machine.
Severity: Moderate
Binary contrasts and negative listing
Rule: Do not define a thing by what it is not. Cut "It's not X. It's Y.", "The question isn't X, it's Y.", "It's not just X but Y.", and stacked denials ("Not a template. Not a starter kit. A design system."). State the positive claim once.
// ❌ Bad
<h2>This isn't a component library. It's a design system.</h2>
// ✅ Good
<h2>A design system with 87 components and its own tokens</h2>
Why it matters: The contrast frame borrows tension it never earns and makes the reader hold a wrong idea before the right one. It is the most-copied shape in generated marketing copy, so it dates the page immediately.
Severity: Moderate
Throat-clearing and faux-insight setups
Rule: Delete the sentence that announces the next sentence. Cut "Here's the thing", "Let me be clear", "I'll be honest", "The uncomfortable truth is", "What most people get wrong", "Here's what nobody tells you", "The part everyone misses". Cut rhetorical setups too: "What if I told you", "Think about it:", "Plot twist:", and self-answered "Question? Answer." pairs. Make the claim stand alone.
// ❌ Bad
<p>Here's what nobody tells you about design systems: distribution is the real problem.</p>
// ✅ Good
<p>Most design systems fail on distribution.</p>
Why it matters: The setup flatters the writer as the lone expert and costs the reader a full sentence before any content arrives. On a hero or a docs page, that sentence is the one the reader actually sees.
Severity: Moderate
Colon reveals and dramatic fragments
Rule: Do not use a noun phrase, a colon, then a lowercase dramatic reveal ("The best part: it learns."). Use colons for lists, labels, and quotations. Do not stack punchy fragments for rhythm ("That's it. That's the whole thing.", "X. And Y. And Z."). Write complete sentences and vary their shape.
// ❌ Bad
<p>The detail that makes it work: a second agent grades the output. That's it. That's the whole system.</p>
// ✅ Good
<p>A second agent grades the output, which is what makes the first one improve.</p>
Why it matters: Both shapes simulate emphasis instead of earning it. Repeated across a page they produce a robotic cadence that reads as generated even when every individual claim is true.
Severity: Moderate
Puffery, weasel attribution and superficial analysis
Rule: State the fact and let the reader judge whether it matters. Cut "stands as a testament", "marks a pivotal moment", "plays a vital role", "solidifies its position", "underscores its significance". Cut unsourced authority: "experts agree", "studies show", "industry reports suggest", "widely regarded as". Name the source or drop the claim; never invent one. Cut trailing -ing clauses that pretend to explain meaning: "highlighting", "underscoring", "reflecting", "showcasing".
// ❌ Bad
<p>The launch adds file search, highlighting the team's commitment to better workflows. Experts agree this marks a pivotal moment for the category.</p>
// ✅ Good
<p>The launch adds file search, so you can find an old draft without leaving the editor.</p>
Why it matters: Puffery asks the reader to accept significance the copy has not shown, and invented attribution is fabricated data rendered as fact, the same failure as a fake testimonial. Both cost trust in every other claim on the page.
Severity: Serious
Fake-profound kickers and recap endings
Rule: Do not end a section with a mic-drop line, an aphorism, or a metaphor that restates the point as something deeper ("The future isn't coming. It's already here."). Do not end with a recap either: "In conclusion", "Ultimately", "Overall", or a final paragraph that repeats the section. Delete the closing line and end on the last concrete sentence, a plain takeaway, or the next action. Do not rewrite a kicker into a better metaphor; remove it.
// ❌ Bad
<p>Ultimately, design is not about pixels. It's about people. The future of interfaces isn't coming. It's already here.</p>
// ✅ Good
<p>Pick a design system, then generate the component set against it.</p>
<Button>Browse design systems</Button>
Why it matters: The reader was just there, so a recap wastes the most valuable position on the page. A fake-profound kicker is worse: it replaces a concrete next step with a sentence that means nothing, exactly where a call to action belongs.
Severity: Moderate
Synonym cycling and weak verbs
Rule: Repeat the correct word instead of rotating synonyms for variety. If it is an agent, call it an agent every time, not "the assistant" then "the tool" then "the system". Replace weak verb phrases with direct verbs: "made a decision" becomes "decided", "has the ability to" becomes "can", "serves as a centralized hub for" becomes "tracks". Prefer "is" and "has" when they are clearer than an inflated verb.
// ❌ Bad
<p>The agent reviews the draft. The assistant then scores the piece. The tool has the ability to suggest fixes.</p>
// ✅ Good
<p>The agent reviews the draft, scores it, and suggests fixes.</p>
Why it matters: Rotating terms makes the reader check whether three things are being described or one. Inflated verbs add length without adding meaning, which is how a two-line explainer becomes a paragraph nobody finishes.
Severity: Moderate
Prose formatting slop
Rule: Formatting follows the content. No emoji in headings. No bold sprinkled mid-sentence for emphasis; use <strong> only to name a literal UI element the reader must find. No bullet list where two sentences of prose read better, and no heading over a two-sentence section. No em dashes at all: they are a rhythm crutch, and a comma, a colon, a period, or parentheses always covers the same job.
// ❌ Bad
<h2>🚀 Getting started</h2>
<ul><li>It's <b>fast</b></li><li>It's <b>simple</b></li></ul>
// ✅ Good
<h2>Getting started</h2>
<p>Install the CLI, then run <strong>Generate</strong>. The first component set takes about a minute.</p>
Why it matters: Decorative formatting is a substitute for structure. Emoji headings and mid-sentence bold are the two fastest visual tells that copy was generated rather than edited, and they survive every token change because they live in the markup, not the theme.
Severity: Moderate
Generated-code tells
Silent error swallows
Rule: A catch that degrades (return null, return [], empty body) must either log the error (console.warn/console.error) or carry a comment stating why silence is correct. Never let an outage wear the same face as empty data: a swallowed database error that returns null makes "user does not exist" and "database down" indistinguishable. Enforced at edit time by .claude/hooks/slop-guard.js and in packages/web by ESLint no-empty.
Why it matters: Generated code reaches for catch { return null } as a universal safety wrapper. Each one deletes a failure signal; together they make the system undebuggable.
Severity: Serious
Cargo-cult timers
Rule: Never use a setTimeout with a magic constant to wait for something to "settle", "be ready", or "have updated" when a real signal exists: an event (transitionend, load), a promise (document.fonts.ready, img.decode()), or a library callback. Reconnect loops need exponential backoff with a cap, not a fixed short interval. If no signal genuinely exists, the comment must say so explicitly. Enforced at edit time by .claude/hooks/slop-guard.js.
Why it matters: A guessed delay is either too short (flaky) or too long (slow), and it hides the real dependency. The canonical incident: a generated 700ms "settle" delay standing in for document.fonts.ready.
Severity: Serious
Dead code and unwired exports
Rule: Every new function, hook, component, or file must be wired to a call site in the same change (see the Wiring Rule in CLAUDE.md). Never keep an unused export "for later", never copy a file as a variant while the original stays live, and never leave a barrel re-export no consumer imports. When removing a feature, remove its helpers, types, and comments in the same commit.
Why it matters: Dead code is worse than no code: it gives a false sense of coverage, rots out of sync with the live path, and doubles the surface every future reader must understand. The 2026 slop audit removed about 2,900 lines of it in one pass.
Severity: Serious
Ethics & Dark Patterns
Whether the interface preserves informed choice. ux/anti-patterns.md (Manipulation & Ethics) covers the reasoning and the Regret and Black Mirror tests; these rules catch the patterns that show up in code.
Fabricated urgency and scarcity
Rule: Countdown timers, stock warnings, and deadlines must reflect real system state. Never hardcode a timer that resets per visit, a "spots left" count with no data source, or an expiring offer that does not expire. If the scarcity is real, bind it to the value that proves it; if it is not real, remove the element.
// ❌ Bad (timer seeded from page load; the "deadline" is fiction)
const [secondsLeft, setSecondsLeft] = useState(15 * 60);
<p>Offer expires in {format(secondsLeft)}. Only 3 spots left!</p>
// ✅ Good (real deadline from the domain, or nothing)
<p>Early-bird pricing ends {format(plan.earlyBirdEndsAt)}.</p>
Why it matters: Invented urgency converts once and burns trust permanently; users who notice the timer reset stop believing every other claim in the product. It also fails the Regret Test: knowing the deadline is fake, no user would rush. Like fabricated social proof, this is invented data rendered as real, so it carries the same severity.
Severity: Critical
Anti-user defaults
Rule: Pre-selected choices must serve the user, not the metric. Marketing emails, data sharing, add-ons, auto-renewal of a higher tier, and anything with a cost or privacy consequence require an explicit opt-in or a previously saved user preference, never a pre-checked box. A default may favor the product only for choices with no cost or privacy consequence, and only when it is also the choice most users would make with full information.
// ❌ Bad (consent harvested through inertia)
<Checkbox defaultChecked name="marketing" label="Send me tips and offers" />
<Checkbox defaultChecked name="shareData" label="Share usage data with partners" />
// ✅ Good (the user opts in)
<Checkbox name="marketing" label="Send me tips and offers" />
Why it matters: Status-quo bias means defaults decide outcomes; a pre-checked box is a decision made for the user, paid for in their money or their privacy. Consent gathered through inertia is resentment on a delay: it does not show an informed choice, and it damages trust when the user discovers it.
Severity: Serious
Confirmshaming and manufactured anxiety
Rule: The decline option in any prompt is neutral and factual ("No thanks", "Not now", "Skip"). Never write the opt-out as self-disparagement, guilt, or fear, and never frame dismissal as a loss the copy invented. Shame-free copy applies to both directions: the accept path states the benefit without inflating it.
// ❌ Bad (guilt-loaded decline)
<Button variant="primary">Yes, protect my account</Button>
<Button variant="ghost">No, I like being at risk</Button>
// ✅ Good (neutral decline)
<Button variant="primary">Enable two-factor auth</Button>
<Button variant="ghost">Not now</Button>
Why it matters: Confirmshaming trades a small conversion bump for the user's respect. The pattern is instantly recognizable, widely mocked, and marks the product as adversarial at exactly the moment it asked for trust.
Severity: Serious
Obstructed exit and cancellation
Rule: Leaving must be as findable as joining. Dismissible surfaces get a visible close affordance; subscriptions cancel in the product when the product owns billing, or hand off visibly to the billing provider's cancellation flow (never "contact support"); unsubscribe and notification controls are one obvious step, not a maze. Never hide, shrink, disable-style, or delay the exit to prolong engagement.
// ❌ Bad (exit disguised as disabled, cancellation routed to a dead end)
<button className="pointer-events-auto text-muted-foreground/30">×</button>
<p>To cancel, contact support and allow 5–7 business days.</p>
// ✅ Good (exit is a first-class action)
<Button variant="ghost" aria-label="Close">×</Button>
<Button variant="outline">Cancel subscription</Button>
Why it matters: Engagement that continues because the exit is hidden is not engagement, it is captivity, and churn metrics built on it are fiction. A clear exit is also what makes staying meaningful.
Severity: Serious
Fabricated social proof and activity
Rule: Activity indicators, viewer counts, testimonials, and "X people bought this" claims must come from real data. Never generate randomized "live" activity, invented reviews, or placeholder testimonials that ship as real. If there is no data yet, show nothing; an empty state is honest, a fake crowd is not.
// ❌ Bad (a random number cosplaying as demand)
<p>{Math.floor(Math.random() * 20) + 5} people are viewing this right now</p>
// ✅ Good (real signal, or none)
{viewerCount > 1 && <p>{viewerCount} people are viewing this</p>}
Why it matters: Fabricated social proof is deception in markup. One discovered fake ("why does the viewer count change on refresh?") retroactively poisons every genuine number the product shows.
Severity: Critical
Fabricated reference prices
Rule: A struck-through price, a "was" price, a percentage saved, or an RRP must reflect a price the product was genuinely sold at, or a real third-party list price. Never hardcode a higher number purely to make the current one look like a discount, never derive it with a multiplier, and never leave a placeholder anchor in shipped markup. If there is no real prior price, show the price alone.
// ❌ Bad (the anchor is invented to manufacture a saving)
<p><s>${(price * 1.6).toFixed(0)}</s> ${price} <span>Save 38%</span></p>
// ✅ Good (a real prior price, or just the price)
{product.previousPrice && <s>${product.previousPrice}</s>}
<span>${price}</span>
Why it matters: Anchoring works because the reader believes the first number was real, so an invented anchor is fabricated data rendered as fact, the same failure as a fake viewer count or a placeholder testimonial. It also carries legal exposure that the other patterns do not: reference-price claims are regulated advertising in the UK, the EU, and many US states.
Severity: Critical
High-stakes actions mixed with frequent ones
Rule: Destructive or irreversible actions (delete, revoke, cancel, transfer) are separated from frequent actions in space and style: never adjacent to a common tap target at the same visual weight, always labeled with the concrete consequence, and confirmed or undoable when the loss is real. The safe option is the prominent one in any confirmation.
// ❌ Bad (delete sits beside the most-used action, same weight)
<Button>Save</Button>
<Button>Delete project</Button>
// ✅ Good (separated, consequence named, safe option leads)
<Button>Save</Button>
...
<Button variant="destructive">Delete project…</Button>
// confirm dialog: "This permanently deletes 12 components." [Keep project] [Delete]
Why it matters: A slip next to a frequent target is a designed accident, and users blame themselves for it. Separation, labeling, and undo turn the worst moment in the product into proof it was built carefully. See ux/errors.md (Confirm destructive actions).
Severity: Serious