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.
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.
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.
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.
Props
StackProps<TTag> is the props of as (default div) plus:
| Prop | Type | Default | Description |
|---|---|---|---|
direction | 'row' | 'column' | 'column' | Exposed as data-direction |
gap | 0 | 1 | 2 | 3 | 4 | 6 | 8 | 2 | Steps 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 |
wrap | boolean | false | flex-wrap |
divider | ReactNode | — | true draws a --border hairline between consecutive children; any other node is rendered inside the divider element instead |
dividerAs | ElementType | 'li' in a list, else 'div' | The divider element |
dividerDecorative | boolean | true | role="none" + aria-hidden; false makes each divider a separator with aria-orientation |
dividerClassName | string | — | Merged last onto every divider, after the hairline classes |
as | ElementType | 'div' | Polymorphic tag or component; its props are typed |
className | string | — | Merged last, after the direction, gap and alignment classes |
Slots
| Slot | Element | Notes |
|---|---|---|
[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
| Token | Used for |
|---|---|
--spacing | Every gap step |
--border | The hairline divider |
Accessibility
- A stack adds no semantics of its own: it renders the tag you pass (
divby default). Useas="ul"/as="ol"for lists,as="nav"for navigation. - Dividers are decorative by default (
role="none",aria-hidden), so a list oflichildren still reads as N items. PassdividerDecorative={false}when the rule separates meaningful groups; it is then arole="separator"witharia-orientation. - Inside a list the divider is an
li, so the markup stays valid.