# Layouts that survive real content

> Stress-test a layout with long words, translations and big text, fix the classic overflow traps, and write CSS that mirrors itself for right-to-left languages.

Module: Layout and pages · Level: intermediate · 8 min · https://school.agents.org.in/design/layout/growth-and-overflow

This card looks finished. Now feed it real-world content: switch on German, a long URL, and 200% text. The CSS is live, so it breaks for real.

*[Interactive demo: Fits the mockup, fails the world. Then apply the fixes.]*

A layout that fits the copy in the mockup has been tested exactly once. Translations lengthen text, readers zoom, names are long and APIs return surprises. **Design for the worst content, not the sample.** That's a stress scenario (One realistic but hostile input for a component: the longest real name, zero items, a 320px box. You render them side by side to see what breaks before users do).

## Grow, don't clip

- **Size text containers with `max-width` and `min-height`**, never a fixed `width` or `height`. A `width: 96px; overflow: hidden` button is fine in English and cut in German.
- **Let rows wrap**: `flex-wrap: wrap; gap: 12px`.
- **If a box must cap its height**, pair `max-height` with `overflow-y: auto`, so the content scrolls instead of vanishing.
- **Short strings are the riskiest.** A one-word button can double in length in translation.
- **Never park a critical action where clipping can reach it**: the bottom of a modal taller than the screen, or anything the on-screen keyboard covers.

Test with pseudo-localization (Replacing interface text with longer, accented fake text (like [Ŝéţţîñĝš ~~~]) to see what breaks when real translations arrive): fake text like `[Ŝéţţîñĝš ~~~~]` that runs 30–40% longer, plus one long real language such as German.

## The overflow traps

Most "why is there a sideways scrollbar" bugs are one of these:

| You see | Fix |
|---|---|
| A grid column or flex child with long text pushes off-screen | `minmax(0, 1fr)` on the track, `min-width: 0` on the child |
| `width: 100vw` scrolls sideways on desktop | `width: 100%` (100vw includes the scrollbar) |
| `height: 100vh` panel cut off on a phone | `100dvh` (next lesson) |
| A modal taller than the screen | `max-height: 100dvh`, a scrolling body, the buttons outside the scroll area |

The first one has a name: min-content blowout (When a grid column or flex item refuses to shrink smaller than its longest word or widest table, pushing the layout off the side of the screen. minmax(0, 1fr) and min-width: 0 fix it). Grid tracks and flex items refuse to shrink below their longest unbreakable thing, so one URL can push a whole layout off the side.

```css
.layout { display: grid; grid-template-columns: 240px minmax(0, 1fr); }
.row > .text { min-width: 0; overflow-wrap: anywhere; }
```

> **overflow: hidden is not a fix.** Clipping the parent hides the sideways scrollbar and the content with it. Find the child that refused to shrink.

## Breakpoints and containers

Break where the layout stops fitting, not at 768px because a preset says so. Write queries in `rem`, so a reader with a larger default font gets the narrow layout sooner.

A reusable component should adapt to its container, not the window: `container-type: inline-size` on the parent, `@container (width < 400px)` on the child. Two traps: a container query can only style the container's children, never the container itself; and a container with no definite width (a shrink-to-fit flex item) collapses to zero.

## Hint at what scrolls

A row of cards that ends exactly at the screen edge looks complete, and nobody scrolls it. Let the next card **peek 16–32px**. A "Show more" button names what it hides: "Show 12 more results". Content scrolling under a sticky header uses `scroll-padding-block-start` so focused fields and anchor targets aren't hidden under it.

## Start and end, not left and right

Arabic and Hebrew read right to left. A layout written in `left` and `right` has to be rewritten for them; one written in *start* and *end* mirrors itself. Switch the direction below.

*[Interactive demo: Physical CSS stays put. Logical CSS mirrors, arrow included.]*

Logical properties (CSS written as start and end instead of left and right (margin-inline-start, text-align: start), so the layout mirrors by itself for right-to-left languages) are the start/end versions of every physical one:

| Physical | Logical |
|---|---|
| `margin-left`, `padding-right` | `margin-inline-start`, `padding-inline-end` |
| `left`, `right` (positioned) | `inset-inline-start`, `inset-inline-end` |
| `text-align: left` | `text-align: start` |
| Tailwind `ml-4`, `pl-4` | `ms-4`, `ps-4` |

Rules for RTL (Languages like Arabic and Hebrew that read from right to left. The whole layout should mirror for them, except things like logos, clocks and play buttons) from Jakub Krehel's work:

- **Keep visual order equal to DOM order.** Moving things with `order` or `row-reverse` makes keyboard and screen-reader order disagree with what's on screen.
- **Mirror only icons that point along the reading direction**: back and forward arrows, reply, progress chevrons. Never a play button, a clock, a checkmark or a logo.
- **Physical stays physical where the glass is physical.** Safe-area insets are about the phone's real left and right edges, so they need an RTL override.
- **Wrap mixed-direction values in `<bdi>` (An HTML tag that isolates a piece of text whose direction may differ from its sentence, such as an English name inside Arabic, so punctuation lands on the right side)**, like the English company name inside the Arabic sentence above, so punctuation lands on the correct side.

*(Flipping `dir` in devtools checks the layout, not the content. Test with real RTL text and an English name inside it.)*

**Stress-test and fix (paste into your agent):**

```text
Stress-test this component and fix what breaks:
- Try pseudo-localized strings (+40%), German labels, a 120-character URL, a 40-character username and 200% text.
- Text containers use max-width/min-height, never fixed width/height; rows flex-wrap with gap.
- Grid tracks minmax(0, 1fr); flex children with text min-width: 0; overflow-wrap: anywhere on user text.
- width: 100% instead of 100vw; no overflow: hidden used to hide a scrollbar.
- Convert left/right to logical properties (margin-inline-start, text-align: start, ms-/ps- in Tailwind). Mirror only direction-bearing icons; wrap mixed-direction values in <bdi>.
```

**Check yourself:** A two-column grid uses grid-template-columns: 240px 1fr, and a long URL in the main column makes the page scroll sideways. What's the fix?

- [ ] overflow: hidden on the body
- [x] grid-template-columns: 240px minmax(0, 1fr), plus overflow-wrap: anywhere on the text
- [ ] width: 100vw on the grid
- [ ] Shrink the font

*Why:* A 1fr track won't shrink below its min-content width. minmax(0, 1fr) lets it, and overflow-wrap lets the URL break inside it.

## In the skill

- https://school.agents.org.in/skill/layout/growth-and-overflow.md
- https://school.agents.org.in/skill/layout/logical-properties-rtl.md
