Skip to contentAsheeUIAsheeUI

Stack

One axis, a token gap, and the two alignment answers, cross-axis placement and main-axis distribution.

A stack lays its children out in a row or a column with one gap between them, and then answers the two questions every row has: where each child sits across the axis, and how the children share the axis. It is the tool for a group of things in the order you wrote them, a toolbar, a form, a card body, a row that pairs a label with an action.

It is one-dimensional on purpose. Grid is the wall of equal columns when the items should wrap themselves, and Split is the pair that stacks below a breakpoint; a stack states one axis and stays on it.

Usage

import { Button, HStack } from "@asheeui/web";

<HStack gap="sm">
  <Button>Save invoice</Button>
  <Button variant="ghost">Cancel</Button>
</HStack>;

gap takes the shared spacing token rather than a number, so a stack's rhythm is the same decision as a section's and a grid's. direction is the axis: column is the default, because most groups read top to bottom.

Direction

A row keeps the children beside each other and a column stacks them; either way the order is the order you wrote, and gap is the only space added between them. Nothing wraps by default, so a row that must break onto a second line states wrap.

The tiles in the demo are align="stretch", so in a row they share the height of the tallest and in a column they share the width of the widest. That is the column default, and it is the reason a column of buttons makes every button as wide as the column, rarely what a row of actions wants, which is why HStack states align="center" and lets each action keep its own width.

Alignment and distribution

align places each child across the axis, at the start, in the center, at the end, stretched to fill it, or on the baseline of their first line of text. justify distributes the free space along the axis: start, center, end, between, around and evenly.

The two are independent, which is why they are two options rather than one arrangement: a header row of a title and an action is align="center" with justify="between", and a column of cards is align="stretch" with justify="start". between is the one to reach for when the first item belongs at one edge and the last at the other; baseline is the one to reach for when a large label sits beside a small one and their text should line up rather than their boxes.

The presets

HStack is a stack with direction="row" and align="center", and VStack is one with direction="column" and align="stretch". Both accept every other stack option, so a preset is a starting point rather than a restriction, and align passed to either one wins over the preset it implies.

The presets state their own direction and alignment one tier below the component configuration, so components.stack.direction and components.stack.align reach an HStack and a VStack as well as a plain Stack: a product whose rows are really columns states it once and both presets follow. Only gap, justify and wrap are registered, which is exactly what leaves configuration able to outrank a preset.

Props

gap md, justify start and wrap false are registered by the component, so components.stack restates the framework's default rhythm once for the whole application. direction and align are deliberately not registered: the hard fallbacks state the plain Stack's column and stretch, HStack and VStack contribute their own pair below the component tier, and a components.stack.direction or .align overrides all three. Registering either would outrank the presets and make them ignore that configuration forever. Nothing resolves from defaultVariant or defaultRadius: a stack has no surface and no corners, it is a relationship between its children, not a box around them.

HStack and VStack take the same props with direction removed, because the preset states it.

Accessibility

  • The element is a plain div with no role, and layout is not semantics: a stack of cards is not a list because it is a stack of cards. When the children are a list, render as="ul" and give it li children, so a screen reader announces how many items there are.
  • gap is a flex gap rather than a margin, so no space is attached to any child: a child that is hidden or absent does not leave a gap behind it.
  • justify distributes space and align places items; neither reorders the children. wrap moves overflow onto a new line without changing the sequence, which is what makes a wrapping toolbar safe to use.
  • align="baseline" lines up the first lines of text rather than the boxes, which is how a heading, a badge and a caption share a row without their different heights looking like a mistake.
  • Nothing here announces anything, so a row whose meaning depends on position, the active tab, the current step, needs that state on the child, not on the stack.

The spacing scale, the width tokens and as are shared by every layout; see Layout and spacing.