Skip to main content

Stack

A stack lays children out along one axis. It is a flex box whose gap comes from the spacing scale (every step a multiple of --spacing), so a density retheme moves every layout together. Use it instead of ad hoc flex gap-* wrappers.

import Stack from '@zuilib/components/stack'

Direction and gap

column (the default) or row. gap is a step of the spacing scale: 0 | 1 | 2 | 3 | 4 | 6 | 8.

Loading example

Align and justify

align sets align-items on the cross axis, justify sets justify-content on the main axis. wrap lets a row break onto further lines.

Loading example

Dividers

divider puts a rule between consecutive children: true draws a --border hairline across the cross axis; any other node is rendered inside the divider element in place of the hairline. null / false children get no divider, and Fragments are flattened, so a conditionally rendered child never leaves a stray rule behind.

Loading example

Combine divider with wrap only in a column: in a wrapping row the vertical rule is a flex item too, so it can land first or last on a line and only spans that line's height.

As a list

as renders another tag or component and types its props. Inside a ul, ol or menu the divider defaults to an li (a list may only contain list items); dividerAs overrides that.

Loading example

Props

StackProps<TTag> is the props of as (default div) plus:

PropTypeDefaultDescription
direction'row' | 'column''column'Exposed as data-direction
gap0 | 1 | 2 | 3 | 4 | 6 | 82Steps of the spacing scale (gap-N, N × --spacing). Exposed as data-gap
align'start' | 'center' | 'end' | 'stretch' | 'baseline''stretch'Cross-axis align-items
justify'start' | 'center' | 'end' | 'between' | 'around' | 'evenly''start'Main-axis justify-content
wrapbooleanfalseflex-wrap
dividerReactNodetrue draws a --border hairline between consecutive children; any other node is rendered inside the divider element instead
dividerAsElementType'li' in a list, else 'div'The divider element
dividerDecorativebooleantruerole="none" + aria-hidden; false makes each divider a separator with aria-orientation
dividerClassNamestringMerged last onto every divider, after the hairline classes
asElementType'div'Polymorphic tag or component; its props are typed
classNamestringMerged last, after the direction, gap and alignment classes

Slots

SlotElementNotes
[data-slot="stack"]div (or as)Root. Also data-direction, data-gap
[data-slot="stack-divider"]div / li (or dividerAs)Each divider. Also data-orientation="horizontal" | "vertical" and data-decorative when decorative
/* Consumer CSS: dashed dividers in every stack */
[data-slot="stack-divider"][data-orientation="horizontal"] {
background: none;
border-top: 1px dashed var(--border);
}

Tokens

TokenUsed for
--spacingEvery gap step
--borderThe hairline divider

Accessibility

  • A stack adds no semantics of its own: it renders the tag you pass (div by default). Use as="ul" / as="ol" for lists, as="nav" for navigation.
  • Dividers are decorative by default (role="none", aria-hidden), so a list of li children still reads as N items. Pass dividerDecorative={false} when the rule separates meaningful groups; it is then a role="separator" with aria-orientation.
  • Inside a list the divider is an li, so the markup stays valid.
  • Container: the page-width wrapper a stack usually sits in.
  • Separator: the same rule as a standalone element.
  • Card: a bounded surface to stack content inside.