Stack
A flex-based layout primitive for arranging items in a row or column. Configurable direction, gap, alignment, justification, wrapping, reversal, dividers, inline display, polymorphic rendering, and LTR/RTL support. Ships with HStack and VStack convenience wrappers.
Playground
Installation
pnpm add @tessinaui/uiUsage
import { Stack, HStack, VStack } from "@tessinaui/ui";{/* Vertical stack (default) */}
<Stack gap="md">
<div>First</div>
<div>Second</div>
<div>Third</div>
</Stack>
{/* Horizontal row */}
<Stack direction="horizontal" gap="sm" align="center">
<Avatar src="/me.png" />
<div>
<p className="font-medium">Jane Doe</p>
<p className="text-muted-foreground">jane@example.com</p>
</div>
</Stack>
{/* Convenience wrappers */}
<HStack gap="sm">…</HStack>
<VStack gap="md">…</VStack>
{/* Dividers between items */}
<Stack
direction="horizontal"
align="center"
divider={<span className="w-px self-stretch bg-border" />}
>
<span>Home</span>
<span>Products</span>
<span>About</span>
</Stack>
{/* Wrapping with reverse order */}
<Stack direction="horizontal" wrap reverse gap="sm">
{tags.map((t) => <Chip key={t}>{t}</Chip>)}
</Stack>
{/* Polymorphic — render as <nav> */}
<Stack
direction="horizontal"
gap="sm"
render={<nav aria-label="Primary" />}
>
<a href="/">Home</a>
<a href="/about">About</a>
</Stack>
{/* RTL */}
<Stack direction="horizontal" dir="rtl" gap="sm">
<span>أول</span>
<span>ثاني</span>
<span>ثالث</span>
</Stack>Examples
Default
A vertical stack (the default direction) with medium gap between children.
Direction
Switch the flow axis with direction — a horizontal row or a vertical column.
Gap
Seven spacing presets, from flush (none) to loose (2xl).
Alignment
Cross-axis align and main-axis justify position children within the stack.
Divider
Pass any node to divider and Stack renders it between each child.
HStack and VStack
Direction-locked convenience wrappers that keep the full Stack API.
Direction
Switch between a column and a row with direction.
| Value | Flex mapping |
|---|---|
"horizontal" | flex-row |
"vertical" (default) | flex-col |
Combine with reverse to flip item order.
direction | reverse | Result |
|---|---|---|
"horizontal" | false | flex-row |
"horizontal" | true | flex-row-reverse |
"vertical" | false | flex-col |
"vertical" | true | flex-col-reverse |
Gap
Controls the spacing between items (and between items and dividers when present).
| Value | Tailwind | px |
|---|---|---|
"none" | gap-0 | 0 |
"xs" | gap-1 | 4 |
"sm" | gap-2 | 8 |
"md" (default) | gap-4 | 16 |
"lg" | gap-6 | 24 |
"xl" | gap-8 | 32 |
"2xl" | gap-12 | 48 |
Align (cross-axis)
Maps to flex align-items. In a vertical stack, this controls the horizontal alignment of children.
| Value | Flex mapping |
|---|---|
"start" | items-start |
"center" | items-center |
"end" | items-end |
"stretch" (default) | items-stretch |
"baseline" | items-baseline |
Justify (main-axis)
Maps to flex justify-content.
| Value | Flex mapping |
|---|---|
"start" (default) | justify-start |
"center" | justify-center |
"end" | justify-end |
"between" | justify-between |
"around" | justify-around |
"evenly" | justify-evenly |
Wrap
wrap={true} enables flex-wrap so items fall to a new line when the container cannot fit them on the main axis. Useful for chip groups, tag clouds, or responsive toolbars.
Divider
Pass any node to divider to render it between each child. Stack handles the interleaving — you just describe the separator.
<Stack
direction="horizontal"
align="center"
gap="md"
divider={<span className="w-px self-stretch bg-border" />}
>
<span>Alpha</span>
<span>Beta</span>
<span>Gamma</span>
</Stack>For vertical stacks, render a horizontal rule instead:
<Stack
gap="md"
divider={<span className="h-px self-stretch bg-border" />}
>…</Stack>Inline
inline={true} swaps flex for inline-flex so the stack can sit inline with surrounding text or flow alongside other inline-block elements.
HStack and VStack
Direction-locked convenience wrappers that preserve the full API otherwise.
<HStack gap="sm">…</HStack> {/* direction="horizontal" */}
<VStack gap="md">…</VStack> {/* direction="vertical" */}They accept every Stack prop except direction.
Polymorphic render
Keep Stack styling while swapping the underlying element for semantic HTML.
<Stack direction="horizontal" gap="sm" render={<nav aria-label="Primary" />}>
<a href="/">Home</a>
<a href="/about">About</a>
</Stack>
<Stack gap="sm" render={<ul />}>
<li>First</li>
<li>Second</li>
</Stack>The passed element's existing className is merged with Stack's generated classes, and its own children (if any) are preserved over the Stack's children.
RTL
dir="rtl" flips the main-axis direction of horizontal stacks and is forwarded to the underlying element. Per-component dir means you can mix LTR and RTL stacks on the same page without changing the global document direction.
Accessibility
-
Stack is a structural layout primitive — it does not apply any ARIA roles on its own.
-
Use
renderto wrap semantic HTML (<nav>,<ul>,<section>,<header>) when the grouping has meaning. Keep<div>(the default) for purely presentational arrangements. -
Dividers are cosmetic. Mark them as
aria-hiddenso they are not announced by screen readers:divider={<span aria-hidden className="w-px self-stretch bg-border" />} -
focus-visiblerings on Stack's children remain visible because Stack sets no overflow clipping.
API Reference
Stack Props
| Prop | Type | Default | Description |
|---|---|---|---|
direction | "horizontal" | "vertical" | "vertical" | Axis on which children flow |
reverse | boolean | false | Reverses child order via flex-*-reverse |
gap | "none" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "md" | Space between children |
align | "start" | "center" | "end" | "stretch" | "baseline" | "stretch" | Cross-axis alignment (align-items) |
justify | "start" | "center" | "end" | "between" | "around" | "evenly" | "start" | Main-axis distribution (justify-content) |
wrap | boolean | false | Enables flex-wrap |
inline | boolean | false | Uses inline-flex instead of flex |
divider | ReactNode | — | Node rendered between each direct child |
render | ReactElement | — | Polymorphic target — clones this element and applies Stack styling to it |
dir | "ltr" | "rtl" | — | Text / flow direction, forwarded to the DOM node |
className | string | — | Additional classes on the root element |
HStack / VStack
Both accept every prop above except direction, which is locked to "horizontal" and "vertical" respectively.
Notes
- Default direction is vertical. This matches the convention of most React layout primitives (Chakra, Radix Themes) and reads naturally in JSX where children are typed top-to-bottom.
align="stretch"is the default. In a column, children expand to fill the horizontal width. Switch to"start","center", or"end"to let children size themselves.- Divider interleaving uses
React.Children.toArray, so keyed lists (.map()output) work without warnings — Stack assigns its own keys to the divider fragments. renderprecedence: when passed, Stack renders the provided element type with its original props merged in, its originalclassNameconcatenated with Stack's generated classes, and its original children (if any) preserved over the Stack'schildren.- No SSR caveats. Stack is a pure layout primitive with no hooks beyond
useMemofor divider interleaving — safe to render on the server.
Split Button
A compound button with a primary action and a dropdown trigger for secondary options
Status
A small circular status indicator for user presence (online, away, busy, neutral) and feedback states (success, info, warning, error). Five sizes from 8 px to 24 px. Used as a standalone badge or embedded inside Avatar.