Actual CSS

Stack

Vertical flow with consistent spacing, ideal for forms, card content, and content blocks.

Account

Manage profile and billing settings.

<section class="stack">
  <h2>Account</h2>
  <p class="muted">Manage profile and billing settings.</p>
  <form>
    <label class="field">
      <span class="field-label">Email</span>
      <input class="input" type="email" placeholder="you@example.com" />
    </label>
  </form>
</section>

Use stack for forms, card content, modal bodies, side panels, and documentation blocks.

Like .cluster, stack owns its children's block-axis spacing: direct-child block margins are reset, so vertical rhythm always comes from --gap. Inline margins stay free — for example margin-inline: auto on a child centers it without fighting the primitive.

Don't wrap full-width regions with .stack

.stack is a column flex container, so a .center child — which sets its own inline-size and margin-inline: auto — becomes a flex item that will not stretch across the full row; it sizes to its content measure and centers inside the flex line. For page-level full-width regions that each constrain their own content, keep them in normal block flow instead:

<header class="center">…</header>
<main class="center">…</main>
<footer class="center">…</footer>

Use .stack for the vertical rhythm between related items inside one region, not as the wrapper that lays out independent full-width regions of a page.

Choosing a rhythm

--gap is the primitive's channel. .tight and .loose set it and nothing else; apply one on the stack to space one instance, or on an ancestor to reach nested layouts. Pick the one that matches how tightly the children belong together:

MarkupGapUse for
.stack gap-none0Lines forming one typographic unit
.stack tight8pxA homogeneous series of controls
.stack12pxNormal flow between distinct elements
.stack loose24pxSections, or blocks that read as separated

Size changes a component's scale. Density (.compact, .spacious) changes UI geometry and rhythm together: gap, padding, control size. Rhythm (.tight, .loose) changes the gap only — closer or farther items, tighter spacing without smaller buttons. Use one rhythm class per element.

One typographic unit means a name and a job title, or a figure and its caption. A homogeneous series means a radio list, a checkbox list, or a compact vertical menu.

The distinction between the first two matters. 42 above Open issues is almost a single block of text, so it takes gap-none. Three .choice labels stay three separate controls that happen to form one group, so they take tight — tighter than the default, but still spaced.

.tight and .loose rebind --gap to --space-20 and --space-50, the same steps .compact and .spacious use. Because the token is inherited, they also tighten the inner gap of any nested stack, cluster, and grid. For a one-off gap that must not reach nested layouts, set the gap property directly on the instance (the next section explains why).

A stack that mixes kinds keeps the default: five .field wrappers and one lone .choice is a form that contains a checkbox, not a list of options, and tightening it would squeeze the fields.

Region rhythm goes on gap, not on --gap

A container that composes other primitives owns its rhythm through the gap property. Setting --gap on it instead re-spaces everything it contains, at any depth:

/* Leaks: --gap is inherited, so every nested stack, cluster and grid — down to
   a radio list inside a field-group — is pushed to 24px too. */
.app-region {
  --gap: var(--space-50);
}

/* Owns its own rhythm and nothing else. Nested primitives keep --gap. */
.app-region {
  gap: var(--space-50);
}

Both give the region the same spacing; only the second stops there. This is what makes a nested .stack tight a real rhythm choice — 12px down to 8px — rather than a patch cancelling a rhythm inherited from three levels up.

Setting --gap is still the right move on the element that consumes it: a one-off --gap on a card body or an actions row tunes that primitive and nothing below it, because the setter is the consumer. The leak only appears when a container sets the token and its children read it.

The rhythm and density classes are the deliberate exception: they set --gap precisely so the context reaches nested layouts.

Grouping items tightly

Stack spacing is a --gap between every direct child; the primitive cannot tighten two specific children by itself. To group a few items into one unit (a title and its description), nest a .stack gap-none:

Ada Meridian Senior designer working on design systems.
Active
<div class="stack">
  <div class="stack gap-none">
    <strong>Ada Meridian</strong>
    <span class="muted">Senior designer working on design systems.</span>
  </div>
  <span class="badge success soft">Active</span>
</div>

The outer stack keeps its rhythm between groups; the inner gap-none unit owns the tight pairing.

For a one-off adjacency — pull a single child closer to its neighbour without adding a wrapper — the escape hatch is app CSS. gap and margin-block are additive, so a negative block margin on the child cancels the following gap:

.stack > .profile-row {
  margin-block-end: calc(var(--gap) * -1);
}

The stack resets child block margins to zero, so this re-adds a margin on that one child only; the rest of the stack keeps its rhythm.

Actual CSS

Search documentation