Loading…
Loading…
Arranges children elements in a vertical or horizontal stack with consistent spacing.
The Stack is a one-dimensional layout primitive that arranges child elements along a single axis — vertical (VStack) or horizontal (HStack) — with consistent spacing between them. Built on CSS Flexbox, Stack is the most-used layout component in modern design systems because the vast majority of UI layout is fundamentally linear: a column of form fields, a row of buttons, a vertical list of cards.
Stack abstracts away the repetitive display: flex; flex-direction: column; gap: 16px pattern into a semantic, composable component. Its power lies in constraint: by limiting itself to single-axis layout with uniform spacing, it eliminates the most common source of layout inconsistency — ad-hoc margins between sibling elements.
When to use a Stack:
When NOT to use a Stack:
Experiment with Flexbox alignment and spacing using the Flexbox Generator. Fine-tune gap values with the Spacing Scale Generator.
| Variant | Flex Direction | Description |
|---|---|---|
| VStack | column | Vertical stack. Children flow top-to-bottom. The default and most common form. |
| HStack | row | Horizontal stack. Children flow left-to-right (or start-to-end in RTL). |
| VStack Reverse | column-reverse | Vertical, bottom-to-top. Useful for chat message lists that scroll to bottom. |
| HStack Reverse | row-reverse | Horizontal, right-to-left. Useful for RTL-first or specific alignment patterns. |
| Size | Gap Value | Use Case |
|---|---|---|
| None | 0 | Tightly coupled elements (icon + label inside a button) |
| Tight | 4px | Dense inline elements (tags, badges) |
| Small | 8px | Compact form fields, inline controls |
| Default | 16px | Standard content spacing |
| Relaxed | 24px | Section spacing, card content |
| Loose | 32–48px | Page section spacing, hero content |
Preview spacing values with the Spacing Scale Generator.
| Align | CSS Property | Description |
|---|---|---|
| Stretch | align-items: stretch | Children fill the cross-axis (default) |
| Start | align-items: flex-start | Children align to the start of the cross-axis |
| Center | align-items: center | Children center on the cross-axis |
| End | align-items: flex-end | Children align to the end of the cross-axis |
| Baseline | align-items: baseline | Children align by text baseline |
| Mode | Description | Use Case |
|---|---|---|
| No wrap (default) | Items overflow if they exceed container width | Fixed-count layouts where overflow is clipped or scrolled |
| Wrap | Items wrap to the next line with consistent gap | Tag groups, button groups, responsive toolbar items |
When a Stack wraps, it begins to behave somewhat like a grid — use Grid if you need precise row/column control in the wrapped state.
| Property | Type | Default | Description |
|---|---|---|---|
direction | 'row' | 'column' | 'row-reverse' | 'column-reverse' | 'column' | Main axis direction |
gap | number | string | 16 | Space between children (CSS gap property) |
align | 'start' | 'center' | 'end' | 'stretch' | 'baseline' | 'stretch' | Cross-axis alignment (align-items) |
justify | 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly' | 'start' | Main-axis distribution (justify-content) |
wrap | boolean | 'wrap' | 'nowrap' | 'wrap-reverse' | false | Whether children wrap to new lines |
inline | boolean | false | Renders as inline-flex instead of flex |
divider | ReactNode | — | Element inserted between each child (e.g., a Divider) |
as | ElementType | 'div' | Polymorphic element override (<ul>, <nav>, <section>, etc.) |
children | ReactNode | — | Child elements to stack |
className | string | — | Custom CSS class |
style | CSSProperties | — | Inline style overrides |
| Custom Property | Description | Default |
|---|---|---|
--stack-gap | Gap between children | 16px |
--stack-direction | Flex direction | column |
--stack-align | Cross-axis alignment | stretch |
--stack-justify | Main-axis distribution | flex-start |
| Token | Role | Typical Value |
|---|---|---|
space.0 | No gap | 0 |
space.1 | Tight gap | 4px |
space.2 | Small gap | 8px |
space.3 | Compact gap | 12px |
space.4 | Default gap | 16px |
space.5 | Medium gap | 20px |
space.6 | Relaxed gap | 24px |
space.8 | Loose gap | 32px |
space.10 | Extra-loose gap | 40px |
space.12 | Section gap | 48px |
space.16 | Page section gap | 64px |
color.border.subtle | Divider color (when using divider prop) | #e5e7eb |
Stack's token usage is almost entirely spacing-focused. The Spacing Scale Generator is the primary tool for calibrating gap values across your system. Use the Flexbox Generator to test alignment and distribution combinations visually.
Like Grid, Stack is a non-interactive layout primitive. Its "states" are responsive behaviors and content-driven adaptations:
| State | Trigger | Effect |
|---|---|---|
| Default | Initial render | Stack renders with specified direction, gap, and alignment |
| Responsive direction | Viewport crosses breakpoint | Direction changes (e.g., row on desktop → column on mobile) |
| Wrapping | Children exceed container width | Items wrap to new lines maintaining consistent gap |
| Empty | No children | Stack renders as an empty container with no visible output |
| Single child | One child element | Stack renders with no gap (gap only applies between siblings) |
The most common responsive Stack pattern switches from horizontal to vertical on smaller screens:
.stack-responsive {
display: flex;
flex-direction: row;
gap: var(--space-4);
}
@media (max-width: 640px) {
.stack-responsive {
flex-direction: column;
}
}
Many design systems provide a responsive or breakpoint prop for this:
<Stack direction={{ base: 'column', md: 'row' }} gap={16}>
<Box>Left</Box>
<Box>Right</Box>
</Stack>
A well-implemented Stack must handle null, undefined, and false children gracefully — these are common in conditional rendering. CSS gap handles this naturally since it only applies between rendered elements. Older margin-based implementations required explicit null filtering.
Stack itself is semantically neutral — it's a <div> with Flexbox styles. Accessibility depends on the semantic HTML used within and as the stack container.
WCAG Success Criteria:
<Stack as="ul"> with <li> children. If they form a navigation, use <Stack as="nav">. The visual grouping created by Stack must be backed by semantic HTML.column-reverse or row-reverse for content where reading sequence matters — screen readers follow DOM order, not visual order. A chat interface using column-reverse must ensure the DOM order still makes logical sense for assistive technology users.row-reverse or column-reverse, keyboard focus follows DOM order. If visual order contradicts DOM order, users will perceive focus as jumping unpredictably. Match DOM order to visual reading order.divider prop, ensure inserted dividers use role="separator" if they convey structure, or role="presentation" if purely decorative. See Divider for details.Semantic Polymorphism:
The as prop is Stack's most important accessibility feature. Use it to render the correct semantic element:
| Content | as value | Why |
|---|---|---|
| List of items | 'ul' or 'ol' | Screen readers announce list count |
| Navigation links | 'nav' | Creates a navigation landmark |
| Page section | 'section' | Creates a section landmark (pair with heading) |
| Form field group | 'fieldset' | Groups related fields with a <legend> |
| Generic container | 'div' (default) | No semantic meaning |
Check that any decorative dividers between stack items don't interfere with screen reader flow using the Contrast Checker to verify their visual treatment.
Do:
as prop to render semantically correct HTML (<ul>, <nav>, <section>)gap={space.4} instead of gap={16})divider prop to insert consistent separators between children without manual placementDon't:
column-reverse or row-reverse for content where reading order matters (WCAG 1.3.2)gap handle all spacing between childrenComposition Patterns:
| Pattern | Structure | Description |
|---|---|---|
| Form layout | VStack > FormField[] | Vertical stack of labelled form fields with consistent vertical spacing |
| Button group | HStack > Button[] | Horizontal row of related action buttons |
| Card content | VStack > Heading + Text + HStack(buttons) | Vertical card layout with nested horizontal button row |
| Page layout | VStack > Header + Main + Footer | Full page vertical structure |
| Inline metadata | HStack > Avatar + VStack(name + date) | User display with avatar beside stacked name/date |
<!-- Vertical stack -->
<div class="stack" style="display: flex; flex-direction: column; gap: 16px;">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</div>
<!-- Horizontal stack -->
<div class="hstack" style="display: flex; flex-direction: row; gap: 12px; align-items: center;">
<button class="btn btn--primary">Save</button>
<button class="btn btn--secondary">Cancel</button>
</div>
<!-- Stack as a semantic list -->
<ul class="stack" style="display: flex; flex-direction: column; gap: 8px; list-style: none; padding: 0; margin: 0;">
<li>Dashboard</li>
<li>Settings</li>
<li>Profile</li>
</ul>
<!-- Stack with dividers -->
<div class="stack" style="display: flex; flex-direction: column; gap: 0;">
<div style="padding: 16px;">Section 1 content</div>
<hr class="divider" role="separator" />
<div style="padding: 16px;">Section 2 content</div>
<hr class="divider" role="separator" />
<div style="padding: 16px;">Section 3 content</div>
</div>
<!-- Responsive stack: row on desktop, column on mobile -->
<div class="stack-responsive" style="display: flex; gap: 24px;">
<div style="flex: 1;">Left content</div>
<div style="flex: 1;">Right content</div>
</div>
<style>
.stack-responsive {
flex-direction: row;
}
@media (max-width: 640px) {
.stack-responsive {
flex-direction: column;
}
}
</style>import React, { Children, Fragment } from 'react';
interface StackProps {
direction?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
gap?: number | string;
align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline';
justify?: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';
wrap?: boolean | 'wrap' | 'nowrap' | 'wrap-reverse';
inline?: boolean;
divider?: React.ReactNode;
as?: React.ElementType;
children: React.ReactNode;
className?: string;
style?: React.CSSProperties;
}
function Stack({
direction = 'column',
gap = 16,
align = 'stretch',
justify = 'start',
wrap = false,
inline = false,
divider,
as: Component = 'div',
children,
className,
style,
}: StackProps) {
const alignMap: Record<string, string> = {
start: 'flex-start', end: 'flex-end', center: 'center', stretch: 'stretch', baseline: 'baseline',
};
const justifyMap: Record<string, string> = {
start: 'flex-start', end: 'flex-end', center: 'center',
'space-between': 'space-between', 'space-around': 'space-around', 'space-evenly': 'space-evenly',
};
const validChildren = Children.toArray(children).filter(Boolean);
const content = divider
? validChildren.map((child, i) => (
<Fragment key={i}>
{child}
{i < validChildren.length - 1 && divider}
</Fragment>
))
: children;
return (
<Component
className={className}
style={{
display: inline ? 'inline-flex' : 'flex',
flexDirection: direction,
gap: typeof gap === 'number' ? `${gap}px` : gap,
alignItems: alignMap[align],
justifyContent: justifyMap[justify],
flexWrap: wrap === true ? 'wrap' : wrap === false ? 'nowrap' : wrap,
...style,
}}
>
{content}
</Component>
);
}
// Convenience wrappers
function VStack(props: Omit<StackProps, 'direction'>) {
return <Stack direction="column" {...props} />;
}
function HStack(props: Omit<StackProps, 'direction'>) {
return <Stack direction="row" {...props} />;
}
// Usage: Card with nested stacks
function UserCard({ user }: { user: { name: string; role: string; avatar: string } }) {
return (
<VStack gap={16} style={{ padding: 24, border: '1px solid #e5e7eb', borderRadius: 12 }}>
<HStack gap={12} align="center">
<img src={user.avatar} alt="" style={{ width: 48, height: 48, borderRadius: '50%' }} />
<VStack gap={2}>
<strong>{user.name}</strong>
<span style={{ color: '#6b7280', fontSize: 14 }}>{user.role}</span>
</VStack>
</HStack>
<HStack gap={8} justify="end">
<button className="btn btn--secondary">Message</button>
<button className="btn btn--primary">Follow</button>
</HStack>
</VStack>
);
}Chakra UI provides <Stack>, <VStack>, and <HStack>. Stack accepts direction (row | column, supports responsive objects like { base: 'column', md: 'row' }), spacing (gap using Chakra's space scale), align (align-items), justify (justify-content), wrap, divider (a React element inserted between children, typically <StackDivider />), and shouldWrapChildren (wraps each child in a <div> for consistent spacing in older browsers). Chakra's Stack is the most-referenced implementation and popularized the divider prop pattern.
Material Design 3 (MUI) provides <Stack> with direction (row | column | row-reverse | column-reverse, supports responsive breakpoint objects), spacing (multiplied by the 8px theme unit), divider (React element), alignItems, justifyContent, and the useFlexGap prop (uses CSS gap instead of margin — this is the modern recommended approach). MUI's Stack is built on top of Box with flex styles.
Ant Design provides <Space> rather than Stack. Props include direction (horizontal | vertical), size (small | middle | large | number), wrap, align (start | end | center | baseline), and split (divider element). Space.Compact is a specialized variant that removes gaps and adjusts border-radius for tightly grouped elements like input + button combinations. Ant's implementation uses margin rather than CSS gap for broader browser support.
Radix UI provides <Flex> in their Radix Themes package with direction, gap, align, justify, wrap, and as (polymorphic). It's a general-purpose Flexbox primitive rather than a Stack-specific component, but serves the same role.
Tailwind CSS handles stacking with utility classes: flex flex-col gap-4 (VStack), flex flex-row gap-3 items-center (HStack). The space-y-{n} and space-x-{n} utilities add margins between children (older pattern), while gap-{n} uses CSS gap (modern pattern). Responsive direction changes use prefix variants: flex-col md:flex-row.
react-aria (Adobe Spectrum) provides <Flex> with direction, gap, alignItems, justifyContent, wrap, and dimension props. Their implementation handles responsive props via a ResponsiveProp type that maps breakpoints to values.