Stack

.stack

.stack — the space between things, owned by the thing that contains them.

Examples

One gap, owned by the container

View “One gap, owned by the container” as

A heading

A paragraph.

Another one, the same distance below.

<div class="stack">
  <h2>A heading</h2>
  <p>A paragraph.</p>
  <p>Another one, the same distance below.</p>
</div>

A component carries no outer margin — it cannot know what comes next — so something has to own the space between two components, and this is that something.

Only the second element of a pair carries a margin, so nothing sums, nothing depends on margin collapsing, and the block ends flush with its container.

Three sizes

View “Three sizes” as

Parts of one thing.

A label and its control.

Sections of a page.

Further apart.

<div class="stack stack-tight">
  <p>Parts of one thing.</p>
  <p>A label and its control.</p>
</div>

<div class="stack stack-loose">
  <p>Sections of a page.</p>
  <p>Further apart.</p>
</div>

.stack-tight for parts of one thing, .stack for blocks, .stack-loose for sections.

The modifiers also work on their own, because a stack-tight that silently did nothing would be the kind of failure that is only ever found by looking at the screen.

The one child that needs more room

View “The one child that needs more room” as

Ordinary.

Pushed further down.

<div class="stack">
  <p>Ordinary.</p>
  <p style="--stack-space: var(--space-12)">Pushed further down.</p>
</div>

--stack-space goes on the child and changes the space above it.

Written as a class in your own stylesheet — .site-note { --stack-space: var(--space-10) } — that is what most hand-written margins were doing anyway, and unlike a margin it cannot go negative behind your back.

Tokens 0

Level 2, declared on .stack itself. Set any of them on that selector to restyle this utility without touching the skin.

None. It reads the skin's tokens directly.

Variants and states

Variants

  • .stack-tight

Inside it

  • .stack-loose

State it reads

None.

Before you ship it

What you have to do 6 requirements

  1. Put it on the container, not on the children. The class goes on the element whose children need separating. On a child it does nothing, because the child has no children of its own to space.
  2. One level. > *, not a descendant selector: a stack spaces its own children and stops. Nesting is how a subtree gets its own rhythm. .prose is the one that goes all the way down, because it styles content nobody can put a class on; this one is for markup you are writing.
  3. Do not put it on a flex or grid container. There the gap is gap's job, and both would apply and add up — and a margin inside a flex container does not collapse with anything, so the composition described above is off too. If you want the stack's rhythm, take the display off; if you want flex, use gap: var(--space-6).
  4. Do not put it on a .prose. class="prose stack" renders two gap systems interleaved, and which one an element gets depends on its depth and its tag. The two utilities answer the same question for two different kinds of content — .prose for markup nobody can put a class on, this one for markup you are writing — and a block is one or the other.
  5. A negative margin is a bug report. If a gap here is wrong, the value is wrong or the element is in the wrong stack. Pulling it back by hand hides which, and it is what left one site with a subtitle overlapping its own title by ten pixels on 4.870 pages.
  6. --stack-space goes on the child. Written on the container it does nothing now, which is an improvement: while it inherited, that mistake read like "the stack's spacing", won against the container's own size for every child, and made .stack-tight inert with nothing reporting it.

src/css/utilities/stack.css · npx mostlycss add stack