layout: form layouts, padding lives only on leaves
Forms are where the leaf-only rule earns its keep: labels, inputs, step bars and edit affordances all want to run edge to edge. Side A pads a container and pays in negative margins; side B hands the padding to the leaves. Turn on the inspector: amber owns padding, red pays for it.
label placement under a width budget
Drag the slider to resize both forms. Names on top survive any width; names beside the input are a grid decision that reflows with a container query, not a per-field edit.
A: names above inputs
gutter tracks inset every field; nothing pads but the controls. it reads the same at 280 and at 540.
B: names beside, reflowing
below 420px the grid collapses to names-on-top by itself: one rule, zero per-field edits.
whether a field's name sits above or beside its input is the form grid's arrangement, never the field's padding. switch placement in one grid rule; if you find yourself editing every field, the padding is on the wrong node.
a multi-step wizard
Step bar, body, footer buttons. A pads the whole wizard; the bar and the footer must hack their way out to run full bleed.
A: the wizard pads
name the workspace — you can rename it later.
the bar escapes up and out; the footer escapes down and out.
B: three bands
name the workspace — you can rename it later.
bar, body and footer are bands; the fields and buttons are leaves inset by gutter tracks and spacers. zero margins.
a wizard is three full-bleed bands. the bar and footer own their edges, and the body insets its field with gutter tracks. when the container pads, every band must escape, and every escape is a negative margin that drifts the day the inset changes.
<div class="lay-wizard"> <!-- container: bands only, zero padding --><div class="lay-steps">…</div> <!-- band; its leaves inset themselves --><div class="lay-wizard-body"> <!-- gutter tracks --><p data-wizard-text>…</p><Input className="col-start-2 w-full" defaultValue="acme-platform" /></div><div class="lay-wizard-foot"> <!-- band; spacer pseudos inset the buttons --><Button variant="ghost" size="sm">back</Button><Button size="sm">next</Button></div></div>.lay-wizard-body { grid; grid-cols-[1.5rem_minmax(0,1fr)_1.5rem]; gap-y-5 }.lay-wizard-body > * { col-start-2 }.lay-wizard:not(.is-inset) > .lay-wizard-foot { grid; min-h-16; justify-between;before/after: w-6 }
a settings page
A nav column and a settings panel with grouped rows. A pads both containers; the active fill and every divider escape.
A: the containers pad
the nav links and both dividers fight the padding; hover fills stop at the inset.
B: rows and links inset
divide-y owns the separators; links and rows pad themselves; zero hacks.
the row and the link are the leaves; the panel and nav only arrange and divide. padding on the container pushes the active and hover fills inward: a fill that stops short of the edge is how you can see the padding sits on the wrong node.
<div class="lay-panel"> <!-- container: divide-y only --><div class="lay-setting"> <!-- row leaf: pads itself --><span class="lay-setting-name">notifications</span><Badge variant="default" className="shrink-0">on</Badge></div>….lay-panel { divide-y }.lay-setting { flex; justify-between; px-4 }.lay-setting-name { py-3 } <!-- x inset comes from the row, once -->
inline edit
Toggle edit mode: a view span swaps for an input in the same footprint. A's cell owns the padding, so the input must escape it to match the span.
A: the cell pads, the input escapes
| ana | admin | active |
| binh | editor | active |
the input climbs out with negative margins to cover the same pixels, and drifts the moment the cell inset changes.
B: view and edit share the footprint
| ana | admin | active |
| binh | editor | active |
span and input both pad identically inside a bare cell; the swap is pixel-stable at any inset.
a view/edit swap is pixel-stable only when a bare cell hosts two leaves that pad identically. pad the cell and the input must escape it with negative margins: correct today, drifted at the next inset change.
<td class="is-slot"> <!-- bare cell: no padding --><span class="lay-view" data-edit="b1">admin</span> <!-- leaf: px-4 py-3 --><input class="lay-edit is-hidden" data-edit="b1"> <!-- leaf: px-4 py-3 --></td>.lay-swap td.is-slot { px-0 py-0 }.lay-swap .lay-view { block; px-4 py-3 }.lay-swap .lay-edit { w-full; bg-zinc-800; px-4 py-3 }
containers arrange, leaves inset. side B: zero negative margins.