295 lines
11 KiB
Markdown
295 lines
11 KiB
Markdown
# Layout — Grid, Space, Rhythm
|
||
|
||
> Layout is the skeleton the user never sees and always feels. A page with a real grid, a real spacing scale, and real breakpoints reads as designed. A page with guessed margins reads as generated. Layout is decided **before** the first component is built — see `SKILL.md` Step 5.
|
||
|
||
---
|
||
|
||
## The Container System
|
||
|
||
Decide container widths once, use them everywhere.
|
||
|
||
| Token | Width | Use |
|
||
|---|---|---|
|
||
| `--container-read` | `65ch`–`72ch` | Long-form body text (measure) |
|
||
| `--container-text` | `720px` | Article intros, single-column sections |
|
||
| `--container-main` | `1200px`–`1280px` | Default page container (nav, hero, features) |
|
||
| `--container-wide` | `1440px` | Index tables, image galleries, data-heavy pages |
|
||
| Full bleed | `100%` | One or two moments per page — a spread, a footer, a manifesto |
|
||
|
||
### Rules
|
||
|
||
- **One container per page, plus at most one full-bleed exception.** Mixing four content widths per page reads as accidental.
|
||
- Side padding: `clamp(20px, 4vw, 48px)` minimum; `clamp(24px, 6vw, 80px)` for editorial and Swiss pages where margins carry the design.
|
||
- **Never let body copy span `--container-main`.** Text columns cap at ~`40ch`–`45ch` inside a wide grid; the grid column holds it, not the container.
|
||
- Content must never touch the viewport edge below 400px — padding scales down, never below 20px.
|
||
|
||
```css
|
||
:root {
|
||
--container-main: 1240px;
|
||
--container-text: 720px;
|
||
--container-read: 68ch;
|
||
--pad-inline: clamp(20px, 4vw, 48px);
|
||
}
|
||
|
||
.container {
|
||
max-width: var(--container-main);
|
||
margin-inline: auto;
|
||
padding-inline: var(--pad-inline);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## The Spacing Scale
|
||
|
||
One scale. Everything is spaced from it. No `margin: 37px`, no `padding: 22px`, no one-off gaps.
|
||
|
||
```css
|
||
:root {
|
||
--sp-1: 4px;
|
||
--sp-2: 8px;
|
||
--sp-3: 12px;
|
||
--sp-4: 16px;
|
||
--sp-5: 24px;
|
||
--sp-6: 32px;
|
||
--sp-7: 48px;
|
||
--sp-8: 64px;
|
||
--sp-9: 96px;
|
||
--sp-10: 128px;
|
||
}
|
||
```
|
||
|
||
### Section rhythm
|
||
|
||
| Viewport | Between sections | Inside a section |
|
||
|---|---|---|
|
||
| Mobile (< 720px) | `--sp-7` (48px) | `--sp-5` – `--sp-6` |
|
||
| Tablet (720–1024px) | `--sp-8` (64px) | `--sp-6` |
|
||
| Desktop (> 1024px) | `--sp-9` – `--sp-10` (96–128px) | `--sp-6` – `--sp-7` |
|
||
|
||
**Rules:**
|
||
|
||
- Space **before** a heading is larger than space after it (roughly 1.5–2×). The heading belongs to the text below it — proximity is hierarchy.
|
||
- If two sections need a divider **and** more space, the spacing was wrong. Whitespace separates; hairlines clarify (tables, indices). Not both everywhere.
|
||
- Space communicates hierarchy: **more space = more importance.** The hero gets the most air on the page. If every section has 128px around it, none of them is the hero.
|
||
|
||
---
|
||
|
||
## Grid Systems
|
||
|
||
### The default: 12 columns
|
||
|
||
```css
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||
column-gap: var(--sp-5);
|
||
}
|
||
```
|
||
|
||
Use it for hero splits and multi-column zones. Not every zone needs all 12 — see splits below.
|
||
|
||
### Editorial / Swiss: 6 columns
|
||
|
||
Wider gutters, fewer columns, stronger verticals. Index pages, archives, tables of contents. Pair with hairline rules and mono metadata — see `editorial-patterns.md`.
|
||
|
||
### Asymmetric splits (the anti-slop move)
|
||
|
||
Equal 50/50 and identical thirds are the default AI output. Offset the split instead:
|
||
|
||
| Split | Effect | Typical use |
|
||
|---|---|---|
|
||
| `5fr / 7fr` | Text-led, support right | Hero: headline left, product/UI right |
|
||
| `3fr / 9fr` | Sidebar + content | Article with meta column, docs |
|
||
| `7fr / 5fr` | Support left, text right | Feature sections alternating with the hero |
|
||
| `4fr / 4fr / 4fr` | **Avoid** — identical thirds | (Only for genuinely equal data: pricing tiers you've already fixed per `anti-patterns.md` §13) |
|
||
| `2fr / 6fr / 4fr` | Meta + body + aside | Editorial spreads, catalog entries |
|
||
|
||
```css
|
||
.hero {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
|
||
column-gap: clamp(32px, 5vw, 80px);
|
||
align-items: center;
|
||
}
|
||
|
||
/* Alternate the next feature section — mirror, don't repeat */
|
||
.feature--flipped { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
|
||
```
|
||
|
||
### The meta-column pattern
|
||
|
||
A workhorse: a narrow fixed column (`180px`–`220px`) for labels, numbers, kickers; the rest for content. It forces asymmetry, gives metadata a home, and scales down to one column on mobile. Used by Pentagram archives, product docs, and every example in `examples/`.
|
||
|
||
```css
|
||
.section {
|
||
display: grid;
|
||
grid-template-columns: 200px minmax(0, 1fr);
|
||
column-gap: clamp(32px, 5vw, 64px);
|
||
}
|
||
|
||
@media (max-width: 720px) {
|
||
.section { grid-template-columns: 1fr; }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Composition Patterns
|
||
|
||
### The dominant element
|
||
|
||
Every page has **one** element that dominates (see `SKILL.md` Step 5): usually the hero headline or the product visual. Compose around it:
|
||
|
||
- Give it the largest type or the largest box on the page.
|
||
- Everything else steps down **deliberately** — second level at ~60% of its size, third at ~35%.
|
||
- One full-bleed or oversized moment per page. Two is noise.
|
||
|
||
### Reading paths
|
||
|
||
- **Z-pattern** for sparse, hero-led pages: strong top-left anchor, diagonal to a CTA bottom-right.
|
||
- **F-pattern** for text-heavy pages: reinforce with a strong left rule — meta column, numbered index, aligned labels.
|
||
- **Single-axis scroll** for editorial: one strong centerline, breaks only for full-bleed spreads.
|
||
|
||
### Alternation
|
||
|
||
Down the page, alternate section structures — never repeat one module twice in a row:
|
||
|
||
```
|
||
hero (5/7 split, type-led)
|
||
→ statement (full-width, large type, no grid)
|
||
→ index (meta-column list)
|
||
→ detail (7/5 split, visual-led)
|
||
→ quote or manifesto (full-bleed or inset)
|
||
→ action (2-column, type + form)
|
||
→ footer (colophon)
|
||
```
|
||
|
||
If two consecutive sections have the same structure, **flip the split or merge them.**
|
||
|
||
### Overlap and inset (use once)
|
||
|
||
An image bleeding out of its column by one gutter (`margin-right: calc(-1 * var(--sp-5))`), or a caption overlapping an image edge, adds craft. Once per page. More is decoration.
|
||
|
||
---
|
||
|
||
## Responsive Strategy
|
||
|
||
**Mobile-first, four breakpoints, tested at five widths.**
|
||
|
||
| Breakpoint | Change what |
|
||
|---|---|
|
||
| Base (320–479px) | Single column, type scale steps down ~1 tier, meta-columns collapse above content |
|
||
| `min-width: 480px` | Two-column utility layouts (stats, small cards), larger touch paddings |
|
||
| `min-width: 768px` | Grid splits appear (5/7 etc.), side nav space, larger section rhythm |
|
||
| `min-width: 1024px` | Full 12-col grid, meta-column pattern, `--sp-9`+ section spacing |
|
||
|
||
Test widths: **320, 375, 768, 1280, 1600.** (`checklist.md` tests 375/768/1280 — 320 catches overflow, 1600 catches lonely stretched content.)
|
||
|
||
### Collapse rules
|
||
|
||
- Multi-column zones collapse **column by column** — meta columns collapse to a top row, not to a wall of centered text.
|
||
- Left-aligned stays left-aligned at every size. Centering is not a mobile strategy.
|
||
- Hide nothing essential on mobile. If a section must be cut, cut it at the brief level, not in CSS.
|
||
- Tables: allow horizontal scroll inside the table wrapper (`overflow-x: auto`), never the page.
|
||
- Fluid type via `clamp()` means most text needs **no** breakpoint overrides — see `typography.md`. Breakpoints are for **structure**, not font sizes.
|
||
|
||
```css
|
||
/* Structure at breakpoints — not font sizes */
|
||
.hero { grid-template-columns: 1fr; }
|
||
|
||
@media (min-width: 768px) {
|
||
.hero { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Whitespace Rules
|
||
|
||
1. Whitespace is a **feature**, not leftovers (`SKILL.md` principle 4). If a section feels crowded, the fix is usually `--sp-9`, not a background tint.
|
||
2. **Air follows importance.** Hero > section intros > body > captions.
|
||
3. Never fill space with decoration because it feels empty. Empty is the design.
|
||
4. Dense is allowed — indices, tables, technical docs are dense **on purpose** (see `aesthetics.md` §3, §6). Density then needs hairline structure and mono numbers to read as order, not crowding.
|
||
|
||
---
|
||
|
||
## Layout Anti-Patterns
|
||
|
||
| ❌ Don't | ✅ Do |
|
||
|---|---|
|
||
| Centered everything, every section | One dominant left-aligned composition; center only short statements |
|
||
| Identical thirds repeated down the page | Asymmetric splits (5/7, 3/9), alternating structures |
|
||
| `max-width: none` full-window text | Container system with a measure for body copy |
|
||
| One-off margins (`17px`, `23px`, `40px`) | The spacing scale, as tokens |
|
||
| Dividers between every section | Whitespace between sections; hairlines inside data only |
|
||
| Hero with 200px padding and 36px headline | Big type or big visual **or** generous air — the hero must justify its space |
|
||
| Every section same structure, same rhythm | Alternate splits and densities; one full-bleed moment |
|
||
| Hiding whole sections on mobile | Simplify structure, keep the content |
|
||
| Fixed pixel widths on grid children (`width: 400px`) | `minmax(0, 1fr)` tracks and `max-width` in `ch`/`%` |
|
||
| Horizontal page scroll from a wide child | `minmax(0, 1fr)` tracks, `overflow-x: auto` on table wrappers, `max-width: 100%` on media |
|
||
| Breakpoints that only change font sizes | Breakpoints change **structure**; type is fluid via `clamp()` |
|
||
|
||
---
|
||
|
||
## A Working CSS Setup
|
||
|
||
```css
|
||
:root {
|
||
--container-main: 1240px;
|
||
--container-text: 720px;
|
||
--container-read: 68ch;
|
||
--pad-inline: clamp(20px, 4vw, 48px);
|
||
|
||
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
|
||
--sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px;
|
||
--sp-9: 96px; --sp-10: 128px;
|
||
|
||
--section-gap: var(--sp-7); /* mobile */
|
||
}
|
||
|
||
@media (min-width: 768px) { :root { --section-gap: var(--sp-8); } }
|
||
@media (min-width: 1024px) { :root { --section-gap: var(--sp-9); } }
|
||
|
||
body { margin: 0; }
|
||
|
||
.container {
|
||
max-width: var(--container-main);
|
||
margin-inline: auto;
|
||
padding-inline: var(--pad-inline);
|
||
}
|
||
|
||
.container--text { max-width: var(--container-text); }
|
||
|
||
section { padding-block: var(--section-gap); }
|
||
|
||
.grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--sp-5); }
|
||
.split { display: grid; column-gap: clamp(32px, 5vw, 80px); }
|
||
.split--5-7 { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
|
||
.split--3-9 { grid-template-columns: minmax(0, 3fr) minmax(0, 9fr); }
|
||
.split--meta { grid-template-columns: 200px minmax(0, 1fr); column-gap: clamp(32px, 5vw, 64px); }
|
||
|
||
.measure { max-width: var(--container-read); }
|
||
|
||
@media (max-width: 767px) {
|
||
.split, .split--5-7, .split--3-9, .split--meta { grid-template-columns: 1fr; row-gap: var(--sp-6); }
|
||
}
|
||
|
||
*, *::before, *::after { box-sizing: border-box; }
|
||
img, svg, video { max-width: 100%; height: auto; }
|
||
```
|
||
|
||
---
|
||
|
||
## Layout QA
|
||
|
||
- [ ] One container system; body copy capped at ~45ch inside grids
|
||
- [ ] All spacing comes from the scale — zero one-off values
|
||
- [ ] Hero is asymmetric or has a deliberate typographic moment
|
||
- [ ] No two consecutive sections share a structure
|
||
- [ ] One full-bleed moment maximum
|
||
- [ ] Tested at 320, 375, 768, 1280, 1600 — no horizontal scroll at any width
|
||
- [ ] Breakpoints change structure, not font sizes
|
||
- [ ] Left alignment preserved at every size
|
||
|
||
See also: `typography.md` (fluid type), `color.md` (surface rhythm between sections), `anti-patterns.md` §6, §11, §12 (structural slop), `checklist.md` (Layout section).
|