---
title: "Layout and spacing (craft)"
description: "Template: <Container> (page), <Container width=\"prose\">, <Container width=\"narrow\">. Never nest containers."
canonical: https://void-design.vercel.app/docs/craft/layout
lastModified: 2026-09-16
---

# Layout and spacing

## 1. Containers

| Utility | Width | Use | Measured band |
|---|---|---|---|
| `container-page` | max 76rem (1216px) incl. fluid gutter 20→40px | default marketing page | 1180–1232 (Stripe, Resend, Clerk, Framer, Granola) |
| `max-w-wide mx-auto px-gutter` | 90rem (1440px) | product-dense, full-bleed galleries, instrument | 1392–1408 (Vercel, Attio, Railway) |
| `max-w-prose mx-auto px-gutter` | 65ch | articles, docs body | 640–720 (Paco 640, Emil 692) |
| `max-w-narrow mx-auto px-gutter` | 40rem | forms, auth, short CTA | — |

Template: `<Container>` (page), `<Container width="prose">`, `<Container width="narrow">`.
Never nest containers. Full-bleed backgrounds go on the `<section>`, the container sits inside.

## 2. Vertical rhythm

- `section-y` = `clamp(4rem, …, 8rem)` → 64px at 360, 128px at 1440. `py-section` is the same value as spacing.
- Vary deliberately. Measured: Granola 112 / 128 / 160; Attio 152 top / 112 bottom; Resend uniform 96 (fine at that size). Recipe per page:

| Section | Classes |
|---|---|
| Hero | `pt-16 pb-20 sm:pt-24 lg:pt-32 lg:pb-28` |
| Proof strip right after hero | `py-10 lg:py-12` |
| Standard section | `section-y` |
| Showcase / signature section | `py-24 lg:py-40` |
| Final CTA band | `py-24 lg:py-32` |
| Footer | `pt-16 pb-10` |

- Inside a section: eyebrow → heading `mt-4`/`mt-5`; heading → lead `mt-5`/`mt-6`; lead → content `mt-12`/`mt-16`. Gap above a heading ≥ 2× the gap below it.
- Mobile section padding 60–96px: `section-y` already clamps; don't add `py-8` overrides.

## 3. Grids

- **12 columns, asymmetric spans.** `grid gap-10 lg:grid-cols-12` with `lg:col-span-7` + `lg:col-span-5`, or label `lg:col-span-3` + content `lg:col-span-9`. Gap = 24–40px (`gap-6`–`gap-10`).
- **Intrinsic card grid** (unknown count): `grid gap-4 [grid-template-columns:repeat(auto-fill,minmax(min(18rem,100%),1fr))]`.
- **Hairline grid** (cells separated by 1px rules, PlanetScale/hellohello): `grid gap-px overflow-clip rounded-xl border border-line bg-line sm:grid-cols-2 lg:grid-cols-3` with each cell `bg-bg p-6`. Only when rows are fully packed; a spanning grid with holes shows the line color, so compute spans.
- **Divided list** (editorial rows): `divide-y divide-line-subtle border-y border-line-subtle`, each row `grid gap-4 py-6 lg:grid-cols-12`.
- **Bento:** spans follow importance (the biggest tile shows the most important real UI). Row-aligned bentos stretch small tiles; use independent columns (`flex flex-col gap-4` per column) when heights differ.
- Page shell uses media queries (`sm md lg`); components inside cards use container queries (`@container` on the parent, `@md:flex-row` on children).

## 4. Alignment

- Left edges align across eyebrow, heading, paragraph and CTA row. Everything aligns to something.
- Center only: a short hero lockup (≤ 3 lines H1, ≤ 2 lines sub) or a final CTA band. Measured interior headings centered: Linear 1/14, Vercel 1/18, Stripe 1/32 (`design` audit flags > 35%).
- Display text optically overhangs: `-ml-[0.04em]` on very large H1s so the stem lines up with body text below.
- Icon + label rows: `inline-flex items-center gap-2`; icon `size-4 shrink-0`; nudge ±1px if the glyph sits high.

## 5. Header and footer

- Header: `sticky top-0 z-40 h-(--header-h)` (56px), `border-b border-line-subtle bg-bg/92`, optional `supports-[backdrop-filter]:bg-bg/80 supports-[backdrop-filter]:backdrop-blur-md`. Measured 56–72px. Nav links `text-sm text-fg-muted hover:text-fg`; CTA `Button size="sm"`.
- Floating inset header (Clerk/Granola `top: 8px`): `sticky top-2 mx-auto max-w-page rounded-xl border border-line-subtle bg-bg/85 backdrop-blur-md`. One or the other, not both.
- Anchors land below the header: base sets `scroll-padding-top` and `[id] { scroll-margin-top }` from `--header-h`. If you change the header height, change `--header-h`.
- Footer: only links that exist. 2–4 columns of `text-sm`, `label-mono` column heads, legal row `text-xs text-fg-subtle`. No newsletter box unless there is a newsletter.

## 6. Density and control sizes

| Size | Height | Text | Padding-x | Use |
|---|---|---|---|---|
| `Button size="sm"` | 32px (`h-8`) + `hit-area` | `text-sm` | 12px | header, inline, tables |
| `Button size="md"` | 36px (`h-9`) | `text-sm` | 14px | forms, cards |
| `Button size="lg"` | 44px (`h-11`) | `text-base` | 20px | hero CTA, touch primary |

Measured: nav CTAs 32px, hero CTAs 40–48px, padding 12–24px, weight 500, never bold. Inputs on mobile use ≥ 16px text (`text-md`) or iOS zooms. Table rows 36–40px.

## 7. Responsive checklist

- Test 390 (phone), 768, 1280/1440, and 320 for overflow. No horizontal scroll at 390 (`design/horizontal-overflow`, `a11y/reflow-overflow`).
- Grids collapse to one column below `lg` unless cells are tiny; keep reading order (content first, media second) with `order-*` only when necessary.
- Full-height: `min-h-svh`, never `h-screen`. Flex children with media or long words: `min-w-0`.
- Safe areas: `container-page` already includes `env(safe-area-inset-left)`.
- Sticky breaks under `overflow: hidden` ancestors; use `overflow-clip`.
- Absolute decorative layers: `pointer-events-none absolute inset-0 -z-10` and `aria-hidden="true"` on the parent section with `relative isolate` (or `grain`, which sets isolation).
