02 / System mechanics
The grid that holds the page.
Layout is the contract between a screen and the components on it. Get the grid, the breakpoints, and the four composition primitives right — and layout stops being a decision anyone makes twice.
01 / Why a grid
Constraint is what makes composition possible.
A grid is a set of vertical lines you commit to using. Twelve is the default because it divides cleanly into 2, 3, 4, and 6 — every common layout (full-bleed, halves, thirds, quarters) snaps to it without remainder. The constraint isn’t a limitation; it’s the reason ten people can ship pages that line up.
The grid lives at the page level. Components don’t care about columns — they care about the parent that places them. Stack, Cluster, Grid, and Sidebar are the four primitives that turn columns into composition.
02 / Twelve columns
Twelve divides into everything.
The twelve-column grid is convention because it composes well: 12 ÷ 2 = halves, ÷ 3 = thirds, ÷ 4 = quarters, ÷ 6 = sixths. Almost every editorial, marketing, and product layout maps to one of these splits — and 12-column markup reads as intent (col-span-8 + col-span-4), not as math.
1200px container
Twelve cells, three composition examples: 8+4 · 4+4+4 · 3+6+3.
03 / Breakpoints
Five widths cover every device.
Tailwind, Bootstrap, and Material each draw the lines in slightly different places, but the shape is the same — phone, tablet, laptop, desktop, wide. Pick one set, tokenize it, and never write a raw pixel value. The grid reflows at each step: 4 columns on mobile, 8 on tablet, 12 from laptop up.
--bp-sm
0–639px · 4 cols · 16px gutter
--bp-md
640–1023px · 8 cols · 24px gutter
--bp-lg
1024–1279px · 12 cols · 32px gutter
--bp-xl
1280–1535px · 12 cols · 32px gutter
--bp-2xl
1536px+ · 12 cols · 40px gutter
One card component, three breakpoint tiers: 1-up on mobile, 2-up on tablet, 3-up from laptop. The card never changes — only the parent grid does.
Mobile-first. Default styles target the smallest width. Each breakpoint adds — never subtracts. A media query that turns something off is a sign the base case was wrong.
04 / Container widths
Width is content-type, not page-type.
A single container width forces marketing pages, prose, and dashboards into the same shape. Different content has different optimal widths — prose at 65ch, marketing hero at 1280px, dashboards full-bleed. The container is a content property, not a page property.
1536px reference
05 / Composition primitives
Four primitives compose every product layout.
From Every Layout (Andy Bell and Heydon Pickering): almost every layout a product needs is some composition of Stack, Cluster, Grid, and Sidebar. Build them once as primitive components — the rest of the system reads as nesting.
Stack
Children stack with consistent gap. The most common layout in any product surface — paragraphs, form fields, card lists.
display: flex; flex-direction: column; gap: var(--space-4)Cluster
Children wrap when they run out of room. For tag rows, toolbars, button groups — anything that's a collection of small things.
display: flex; flex-wrap: wrap; gap: var(--space-2)Grid
Cells of equal width that reflow at breakpoints. Card grids, image galleries, dashboard tiles.
display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr))Sidebar
One fixed column, one fluid column. Marketing pages, docs pages, settings — anywhere a navigation rail meets content.
display: grid; grid-template-columns: 240px 1frThe parent owns the layout. Components never set their own margins — a Card knows nothing about being in a Stack. That’s why the same Card composes inside a Grid, a Cluster, or a Sidebar without modification.
06 / Anti-patterns
Four ways a layout system fights itself.
All four look like the obvious move until the system has to change.
Fixed-width containers
Avoid
max-width: 1200px on every pagePrefer
max-width per content type · prose 65ch · full-bleed editorialMarketing pages, prose, and dashboards have different optimal widths. One container width forces everything into a shape it doesn't want — wide marketing or narrow dashboards.
Layouts inside components
Avoid
Card has 32px margin-topPrefer
Card sits inside a Stack with gap: 32pxComponents shouldn't know what's around them. Layout belongs to the parent so the child stays composable in every context — no margin override, no last-child hack.
Magic-number breakpoints
Avoid
@media (min-width: 873px)Prefer
@media (min-width: var(--bp-md))Cherry-picked breakpoints fragment the system. A handful of tokens cover every real device — adding a custom width for one page becomes the start of a maintenance nightmare.
Mobile as a separate site
Avoid
Hide the desktop nav and ship m.example.comPrefer
Same components · same data · responsive at the layout layerTwo codebases, two source-of-truths, two bugs. Modern layouts shrink and reflow — the same Stack and Grid primitives work at every width with breakpoint-aware sizing.
Layout belongs to the parent. Components ship margin-free.
Container width is per content type, not per page.
Compose Stack · Cluster · Grid · Sidebar — don't reinvent.
Layout decides shape. accessibility decides reach.
A pixel-perfect grid that nobody can use isn’t a system. The accessibility foundation covers the four WCAG principles, keyboard navigation, semantic HTML, and screen-reader testing — the work that makes the layout reach everyone.