Stack

Stack puts space between things. It is a flex container with one job, and it is the answer to most "how do I space these out" questions — vertically by default, horizontally with direction: 'row'.

Gaps come from the spacing scale, so the rhythm of a page stays consistent without anyone picking pixel values.

Basic stack

First
Second
Third
stack({ gap: 'md' },
  card(cardBody('First')),
  card(cardBody('Second')),
  card(cardBody('Third')),
)

Direction

stack({ direction: 'row', gap: 'md' },
  button('One'),
  button({ variant: 'outline' }, 'Two'),
  button({ variant: 'outline' }, 'Three'),
)

Gap

A token name ('xs''3xl'), a number of spacing units, or a raw CSS length.

xsxsxs
mdmdmd
6 units6 units
3rem3rem
stack({ gap: 'lg' },
  stack({ direction: 'row', gap: 'xs' }, chip('xs'), chip('xs'), chip('xs')),
  stack({ direction: 'row', gap: 'md' }, chip('md'), chip('md'), chip('md')),
  stack({ direction: 'row', gap: 6 }, chip('6 units'), chip('6 units')),
  stack({ direction: 'row', gap: '3rem' }, chip('3rem'), chip('3rem')),
)

Alignment

align and justify take raw flexbox values, so anything CSS understands works.

startend
and aligned
stack({ gap: 'md' },
  stack({ direction: 'row', gap: 'sm', justify: 'space-between', style: 'background: var(--su-surface-2); padding: 0.6rem; border-radius: 0.5rem' },
    chip('start'),
    chip('end'),
  ),
  stack({ direction: 'row', gap: 'sm', justify: 'center', align: 'center', style: 'background: var(--su-surface-2); padding: 0.6rem; border-radius: 0.5rem' },
    button({ size: 'sm' }, 'Centred'),
    chip('and aligned'),
  ),
)

Wrapping

A row of chips or buttons that might not fit needs wrap — without it they squash instead of moving to the next line.

routingdataislandsimagespagefindlighthousesitemaprss
stack({ direction: 'row', gap: 'sm', wrap: true },
  ...['routing', 'data', 'islands', 'images', 'pagefind', 'lighthouse', 'sitemap', 'rss'].map(
    (name) => chip({ color: 'neutral' }, name),
  ),
)

Inline

inline makes the stack an inline-flex, so it sits in a line of text rather than taking the full width.

Built with

sitelovite
and nothing else.

text(
  'Built with ',
  stack({ direction: 'row', gap: 'xs', inline: true, align: 'center' },
    chip({ color: 'primary', size: 'sm' }, 'sitelo'),
    chip({ color: 'neutral', size: 'sm' }, 'vite'),
  ),
  ' and nothing else.',
)

As another element

stack({ as: 'nav', direction: 'row', gap: 'sm' },
  navLink({ href: '/docs' }, 'Docs'),
  navLink({ href: '/ui', current: true }, 'UI'),
  navLink({ href: '/examples' }, 'Examples'),
)

Props

PropTypeDefaultDescription
direction'row' | 'column''column'Main axis.
gapSpace'md'Space between children.
alignstring'stretch'Any align-items value.
justifystring'flex-start'Any justify-content value.
wrapboolean | stringfalsetrue means wrap; a string is passed through as flex-wrap.
inlinebooleanfalseRenders as inline-flex.
asstring'div'Element to render, e.g. nav or ul.