Overview
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:
- For vertically stacking page sections, form fields, or content blocks
- For horizontally arranging buttons, tags, badges, or inline elements
- For any layout where children are arranged along a single axis with uniform gaps
- As the primary building block inside more complex layouts (cards, list items, dialogs)
When NOT to use a Stack:
- For two-dimensional layouts (rows AND columns) — use a Grid
- When children need to span multiple rows or columns — use a Grid
- For overlapping or absolutely positioned elements — use raw CSS positioning
- When spacing between items varies by item — use individual margin/padding or a custom layout
Experiment with Flexbox alignment and spacing using the Flexbox Generator. Fine-tune gap values with the Spacing Scale Generator.
Variants
Direction Variants
| 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. |
Spacing Variants
| 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.
Alignment Variants
| 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 |
Wrapping Behavior
| 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.
Properties
Stack Properties
| 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 |
CSS Custom Properties
| 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 Mappings
Design Token Mappings
| 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.
States
Stack States
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) |
Responsive Direction Pattern
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>
Null Child Handling
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.
Accessibility
Accessibility Requirements
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:
- 1.3.1 Info and Relationships (Level A): Stack provides no semantic structure. If stacked items form a list, use
<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. - 1.3.2 Meaningful Sequence (Level A): DOM order must match visual order. Avoid
column-reverseorrow-reversefor content where reading sequence matters — screen readers follow DOM order, not visual order. A chat interface usingcolumn-reversemust ensure the DOM order still makes logical sense for assistive technology users. - 2.4.3 Focus Order (Level A): When using
row-reverseorcolumn-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. - 1.4.10 Reflow (Level AA): At 320px viewport width, horizontal Stacks must reflow to vertical. Use responsive direction changes or wrapping to prevent horizontal scrolling. Test at 400% zoom.
- 4.1.2 Name, Role, Value (Level A): When using the
dividerprop, ensure inserted dividers userole="separator"if they convey structure, orrole="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.
Usage Guidelines
Usage Guidelines
Do:
- Use Stack as your default layout component for single-axis arrangements — it handles 80% of layout needs
- Use the
asprop to render semantically correct HTML (<ul>,<nav>,<section>) - Pair Stack with design tokens for consistent spacing (
gap={space.4}instead ofgap={16}) - Use the
dividerprop to insert consistent separators between children without manual placement - Switch from row to column at appropriate breakpoints for responsive layouts
- Combine Stack with Grid for complex layouts: Grid for the page structure, Stack for the content within each grid cell
Don't:
- Use Stack when you need two-dimensional layout — use Grid instead
- Use
column-reverseorrow-reversefor content where reading order matters (WCAG 1.3.2) - Mix Stack and manual margins — let
gaphandle all spacing between children - Nest too many Stacks deeply without considering whether a Grid would be cleaner
- Use Stack for overlapping or absolutely positioned elements
- Forget that wrapping HStacks create multi-line layouts that might be better served by Grid
Composition 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 |
Code Snippets
<!-- 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>
);
}Design Systems
Design System Implementations
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.