Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
The English summary is being prepared. Showing the original description.
Use when you build or change a layout, page, section, navigation, table, form or image - mobile-first rules, breakpoints and the overflow traps.
The English summary is being prepared. Showing the original description.
See how to installReview the instructions the agent will receive before you install.
Every layout is designed at 360px first, then widened. Desktop-first layouts that get "patched down" with max-*: overrides are where almost every overflow bug comes from, because the narrow case was never actually built — it was reverse-engineered from the wide one under time pressure.
Core principle: Build the 360px layout first, make it correct, then add md:/lg: to use the extra space. Never write desktop styles first and patch down.
sm: (640) md: (768) lg: (1024) xl: (1280) 2xl: (1536) add capability upward — they never subtract it.max-*: as the primary layout strategy — it means the base case is the wrong one.mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 for page content; narrower for prose (max-w-prose / max-w-3xl) in marketing sections.py-12 md:py-16 lg:py-24 — more space grows with viewport, it doesn't appear from nothing at md.grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 for card/list grids; grid-cols-[repeat(auto-fit,minmax(min(100%,16rem),1fr))] when the item count is unknown and a fixed column count would leave gaps or force scroll (ui-guard's allow-listed arbitrary value).flex flex-wrap gap-2 for chip/tag/button rows — never a fixed-height row that clips.lg (flex flex-col lg:flex-row), sidebar full-width on phone, fixed-width only at lg: and up.@container on the wrapper, @md:/@lg: on the organism — instead of the page's breakpoints; the viewport can be 1440px while the card is 300px wide.Each trap below was reproduced in a real browser at 360–1440px and its fix confirmed clean.
| Symptom | Cause | Fix |
|---|---|---|
| Flex/grid item refuses to shrink, pushes siblings off-screen | Flex/grid items default to min-width: auto; a truncate/whitespace-nowrap descendant sets that item's min-content to its full unbroken width | min-w-0 on the item that holds the text (not just the text itself); shrink-0 on siblings that must stay fixed |
| Page scrolls sideways with no single element flagged as overflowing | A long word or URL with no space for the browser to wrap on — the block keeps its layout width while the text spills out of it, so browser_set_viewport reports that the page scrolls horizontally but names no overflowing element. Search the page for long unbroken strings | break-words (overflow-wrap: anywhere) on the text node; never break-all (it breaks mid-word even when wrapping wasn't needed) |
| A panel is too wide below some breakpoint | A literal pixel width (w-[600px]) | w-full + a max-w-* cap so it still has a ceiling on wide screens |
| Image forces horizontal scroll | Something overrides Tailwind v4's own preflight, which already sets img, video { max-width: 100%; height: auto } — a stray max-w-none, a reset library, or a hard inline maxWidth | Give the image w-full h-auto max-w-full, explicit width/height attributes (prevents CLS), and object-cover + aspect-* when it fills a fixed box. A bare <img> with no extra classes does not overflow by default in Tailwind v4 — if one does, something upstream overrides preflight |
| A row of buttons/chips runs off the right edge | whitespace-nowrap on the row instead of letting items wrap | flex flex-wrap gap-2 — wrap the row, don't force it onto one line |
| A decorative shape (blob, ring, gradient) pokes past the viewport edge even on desktop | An absolutely-positioned decoration with a negative offset (-right-10) and no clip on its own stacking context | overflow-x-clip on the section that owns the decoration, never on body (that breaks every position: sticky on the page) |
Full-bleed section overflows as soon as it's not a direct child of body | w-screen (100vw) ignores every ancestor's padding/offset — it ties to the viewport, not the nearest positioned parent, so it overflows the moment it's nested inside anything with horizontal padding | w-full (ties to the parent instead); reach for w-screen only with a negative-margin bleed technique on a direct full-width wrapper, and test it |
grid-cols-3 (or any fixed count) is unusable at 360px | Short, wrapping text in a 3-up grid just looks cramped — it does not overflow by itself. It becomes an actual overflow once any cell holds one long unbreakable token (a code snippet, a slug, a username): grid items default to min-width: auto too, so that one cell's min-content can force the whole track wider than the viewport | grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 (mobile-first column count) and min-w-0 break-words on every cell, so neither the column count nor a stray long token can force the overflow |
| A data table forces the whole page to scroll sideways | The <table> laid out directly in the page flow, no scroll boundary around it | Wrap it in its own overflow-x-auto (table keeps min-w-* so columns stay legible; the wrapper — not the page — scrolls), or replace it with stacked cards below md for simpler tables. Comparison/pricing tables: cards below md, or a sticky first column. A scroll wrapper is keyboard-reachable: tabIndex={0}, role="region" and an aria-label naming the table (axe scrollable-region-focusable fails without it). The page-level horizontal-scroll check correctly stays clean here — the wrapper scrolls, the page does not |
| 100vh content gets cut off under mobile browser chrome | h-screen/100vh is measured before the address bar collapses, so content sits behind it | min-h-dvh (never h-screen) |
| Long compound words (German, Turkish) blow out a heading's line | No wrap/hyphenation hint, and the heading's container is width-constrained | text-balance on the heading, hyphens-auto with the correct lang on the element |
text-3xl md:text-5xl rather than one fixed size. If the design calls for continuous scaling, do it through a token-defined clamp(), not an ad hoc one in a className.<input>/<textarea> text stays ≥16px (text-base) on phone — smaller triggers iOS's auto-zoom on focus, which is its own kind of layout break.md into a menu button (aria-expanded, aria-controls) opening a Radix Dialog/Sheet or disclosure panel; it closes on link click and Escape.sticky top-0 z-20 (z-20 is the sticky-header band of the fixed z-scale: 10 raised · 20 sticky header · 30 dropdown · 40 overlay · 50 modal/toast). Anchored sections under it need scroll-mt-20 (or whatever matches the header's height) so #section links don't land underneath it.env(safe-area-inset-*) so they don't sit under a notch or home indicator.aria-expanded/aria-controls, Radix Dialog/Sheet for the panel (focus trap and body scroll lock come free), closes on link click and Escape.min-h-11 min-w-11 or padding to reach it), ≥24px on desktop.@media (hover: hover) so touch devices don't get a stuck hover state.touch-action: manipulation on interactive elements removes the ~300ms double-tap-to-zoom delay.srcset/sizes, or <picture> with art-directed breakpoints, so phones don't download the desktop asset.loading="lazy" below the fold; the hero/LCP image stays eager.aspect-* (or explicit width/height) reserves the box before the image loads, preventing layout shift.A pricing comparison section, written desktop-first, has three overflow bugs:
// ❌ desktop-first: three columns, a fixed-width badge, and a long plan name that doesn't wrap
<section className="grid grid-cols-3 gap-6 p-8">
{plans.map(plan => (
<div key={plan.id} className="border p-6">
<div className="flex gap-2">
<h3 className="font-semibold">{plan.name}</h3>
<span className="whitespace-nowrap rounded bg-primary px-2 py-1 text-primary-foreground">
Most popular
</span>
</div>
<div className="w-[280px] mt-4">{plan.description}</div>
</div>
))}
</section>
Bug 1: grid-cols-3 with no mobile step — three ~110px columns at 360px. Bug 2: the h3 + whitespace-nowrap badge are flex siblings with no min-w-0, so a long plan name pushes the badge (and the row) past the viewport edge. Bug 3: w-[280px] is wider than the phone viewport itself.
// ✅ mobile-first: one column on phone, badge wraps with the title, fluid width
<section className="grid grid-cols-1 gap-6 p-4 sm:grid-cols-2 lg:grid-cols-3 lg:p-8">
{plans.map(plan => (
<div key={plan.id} className="border p-6">
<div className="flex min-w-0 flex-wrap items-center gap-2">
<h3 className="min-w-0 break-words font-semibold">{plan.name}</h3>
<span className="shrink-0 rounded bg-primary px-2 py-1 text-primary-foreground">
Most popular
</span>
</div>
<div className="mt-4 w-full max-w-sm">{plan.description}</div>
</div>
))}
</section>
max-*:/media-query patches to make it survive at 360px.w-screen on anything that isn't a direct, unpadded child of body.max-w-none instead of sizing the image on purpose.grid-cols-N/flex rows with no mobile step, found only when real (long) content is dropped in.overflow-x-clip/hidden on body to "fix" an overflow instead of finding and fixing the element causing it.grid-cols-2/grid-cols-3/grid-cols-4 on a page-level grid.w-[, h-screen, or w-screen anywhere in the diff.truncate or whitespace-nowrap text with no min-w-0 on an ancestor in the same flex/grid context.<table> with no overflow-x-auto wrapper and no stacked-card fallback.No reviews yet. Tell others what you thought.
No login needed. Share how it worked for you, ask a question, or note where it did not run.
No comments yet. Be the first to write one.
Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
The English summary is being prepared. Showing the original description.
Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping
The English summary is being prepared. Showing the original description.
Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal
The English summary is being prepared. Showing the original description.
Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task
The English summary is being prepared. Showing the original description.
How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.
The English summary is being prepared. Showing the original description.
Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here
The English summary is being prepared. Showing the original description.