layout: content layouts

Six content patterns, each shown wrong (the container pads) and right (only leaves pad). Containers query their own width with container queries, not the viewport. Tick the inspector to see who owns padding.

leaf pad negative-margin hack

reading column and breakouts

Three grid tracks: the middle one caps the measure at 40rem, the outer ones absorb breakouts.

A: the article pads

hero · full bleed

the reading track keeps the measure comfortable. a padded container cannot express that: everything inside shares one inset.

figure · full bleed

prose after the figure.

note: breakouts need negative margins to escape the padding.

closing prose.

every breakout drags a negative margin, and the measure is padding-dependent: change the inset and the line length changes.

B: the leaves pad

hero · full bleed

the reading track keeps the measure at 40rem no matter how wide the frame gets. breakouts span the other tracks.

figure · full bleed

prose resumes in the track.

note: the callout spans all three tracks, no margins involved.

closing prose.

the grid owns arrangement, leaves own padding. bleed is a span, not a margin.

ruling

Reading measure is a grid track (minmax with 0!), not a padding side effect. Breakouts change col-span; they never cancel padding.

B markup
<div className="grid grid-cols-[1fr_minmax(0,40rem)_1fr] gap-y-4">
<div className="col-span-3 …">hero · full bleed</div> {/* breakout: spans all 3 tracks */}
<p className="col-start-2 px-4">…</p> {/* leaf: track 2, pads itself */}
<div className="col-span-3 border-l-4 …">note</div> {/* breakout leaf: spans + pads */}
</div>

landing

Hero, feature grid, cta band. The landing queries its own width: drag the slider and the feature columns re-flow.

A: the landing pads

ship layouts that hold

one rule: containers arrange, leaves pad.

container queries
auto-fill tracks
gap over margins
ready?

the cta band needs negative margins to touch the rounded edges, and the hero gradient stops at the padding.

B: the leaves pad

ship layouts that hold

one rule: containers arrange, leaves pad.

container queries
auto-fill tracks
gap over margins
ready?

the band is a container: it stretches edge to edge and its leaves pad inside it. the hero fill reaches the top because the landing pads nothing: the hero button is inset by a spacer row, the band button by a spacer track.

ruling

Full-bleed bands and hero fills are the first sign a container pads. On the leaf-padded side they are free.

B markup
<div className="@container flex flex-col overflow-hidden rounded-xl"> {/* bands only, zero padding */}
<div className="flex flex-col items-center after:h-10 after:content-['']"> {/* band; leaves inset */}
<b className="px-6 pt-10">…</b>
<Button size="sm">get started</Button>
</div>
<div className="mt-8 grid grid-cols-[1fr_auto_auto] items-center after:w-8 after:content-['']"> {/* spacer track */}
<b className="px-8 py-6">ready?</b>
<Button variant="outline">start free</Button>
</div>
</div>

pricing tiers

The ribbon and the foot are part of the card surface, so the card cannot pad.

A: the tier pads

starter$01 project
most popular
pro$20unlimited projects
billed yearly
team$60sso + audit log

ribbon and foot fight the padding with negative margins; the featured card's border shifts everything inside by 2px.

B: the leaves pad

starter$01 project
most popular
pro$20unlimited projects
billed yearly
team$60sso + audit log

ribbon and foot are leaves: they pad themselves and sit flush against the card edges. the featured outline is a ring, so it shifts nothing inside.

ruling

Ribbon and foot are surface leaves: they cannot exist inside padding, so the tier never pads. The button is inset by a spacer row; a featured outline is a ring, which draws without shifting layout.

B markup
<div className="flex flex-col overflow-hidden rounded-xl"> {/* container: zero padding */}
<div className="bg-indigo-500 px-6 py-1 text-center text-xs">most popular</div> {/* leaf: flush */}
<b className="px-6 pt-3 text-2xl">$20</b> {/* leaf: pads itself */}
<Button size="sm">choose</Button>
</div>
{/* button inset: spacer row, not margin; featured outline: inset ring */}
<div className="… after:h-6 after:content-['']" />
<div className="… ring-2 ring-inset ring-indigo-500" />

docs layout

Nav, content, toc. The active nav item's indicator sits on the pane edge, so the nav cannot pad.

A: the columns pad

layout

containers arrange, leaves pad.

pnpm add tailwindcss

the active item's left indicator is stranded inside the padding; the code block needs a negative margin to touch both pane edges.

B: the leaves pad

layout

containers arrange, leaves pad.

pnpm add tailwindcss

the indicator hugs the pane edge; the code block is a leaf with its own padding.

ruling

Any indicator or rail drawn on a pane edge (active borders, toc rules) requires an unpadded pane. Padding would strand it.

B markup
<div className="flex overflow-hidden rounded-xl"> {/* container: three panes, zero padding */}
<nav className="flex w-48 shrink-0 flex-col border-r"> {/* pane: unpadded so the rail hugs the edge */}
<span className="border-l-2 border-indigo-400 px-4 py-1">layout</span>
</nav>
<div className="flex min-w-0 flex-1 flex-col gap-3">
<p className="overflow-x-auto bg-zinc-950 px-6 py-3">pnpm add tailwindcss</p> {/* leaf: pads itself */}
</div>
</div>

feed and timeline

The rail is a grid column, not an absolutely positioned line hunting for dots by magic offsets.

A: absolute rail

deploy 47 passed 2m ago
comment from mira 18m ago
release 2.4 tagged 1h ago

the rail guesses its offsets from the padding; move anything and the dots drift off the line.

B: rail as a column

deploy 47 passed
2m ago
comment from mira
18m ago
release 2.4 tagged
1h ago

each row is one grid: the rail cell draws top-cap, dot, and the rest of the line. the line cannot miss the dot.

ruling

Absolutely positioned connectors are a smell. If a line must pass through dots, give the dots a shared column and let the column draw the line.

B markup
<div className="grid grid-cols-[2.5rem_1fr]"> {/* one grid per row: rail column + text */}
<span className="flex flex-col items-center"> {/* the column draws the line */}
<span className="h-4 w-px bg-zinc-700" />
<span className="size-2 rounded-full" />
<span className="w-px flex-1 bg-zinc-700" />
</span>
<span className="py-2 pr-4"><b>deploy 47 passed</b></span> {/* leaf pads itself; no absolute rail */}
</div>

kanban

Horizontal scroll; the sticky column header sits flush because the column, not the board, is the header's container. Resize the board and drag it sideways.

A: the board pads

todo · 3
card a
card b
card c
doing · 1
card f
review · 0
done · 0

the sticky header never reaches the column's rounded top: the board padding and column padding both get in the way.

B: the columns pad

todo · 3
card a
card b
card c
doing · 1
card f
review · 0
done · 0

the header is the column's first leaf: sticky, flush with the rounded top. the body insets its cards with gutter tracks, not margins.

ruling

The sticky header is why the column cannot pad: a padded column pushes the sticky fill off the rounded edge. The scroll body arranges cards with gutter tracks, so cards inset without margins.

B markup
<div className="flex w-56 shrink-0 flex-col overflow-hidden rounded-lg"> {/* column: zero padding */}
<div className="bg-zinc-700 px-3 py-2">todo · 3</div> {/* first leaf: sticky, flush with the top */}
<div className="grid flex-1 grid-cols-[0.75rem_minmax(0,1fr)_0.75rem] gap-y-2
before:col-span-3 before:h-2 before:content-['']
after:col-span-3 after:h-2 after:content-['']"> {/* gutter tracks + spacer rows */}
<div className="col-start-2 rounded-md bg-zinc-950 px-3 py-2">card a</div>
</div>
</div>

search pages

go to any page