Skip to content

Stack

Arranges children elements in a vertical or horizontal stack with consistent spacing.

  • VStack
  • HStack
  • Flex Stack

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

VariantFlex DirectionDescription
VStackcolumnVertical stack. Children flow top-to-bottom. The default and most common form.
HStackrowHorizontal stack. Children flow left-to-right (or start-to-end in RTL).
VStack Reversecolumn-reverseVertical, bottom-to-top. Useful for chat message lists that scroll to bottom.
HStack Reverserow-reverseHorizontal, right-to-left. Useful for RTL-first or specific alignment patterns.

Spacing Variants

SizeGap ValueUse Case
None0Tightly coupled elements (icon + label inside a button)
Tight4pxDense inline elements (tags, badges)
Small8pxCompact form fields, inline controls
Default16pxStandard content spacing
Relaxed24pxSection spacing, card content
Loose32–48pxPage section spacing, hero content

Preview spacing values with the Spacing Scale Generator.

Alignment Variants

AlignCSS PropertyDescription
Stretchalign-items: stretchChildren fill the cross-axis (default)
Startalign-items: flex-startChildren align to the start of the cross-axis
Centeralign-items: centerChildren center on the cross-axis
Endalign-items: flex-endChildren align to the end of the cross-axis
Baselinealign-items: baselineChildren align by text baseline

Wrapping Behavior

ModeDescriptionUse Case
No wrap (default)Items overflow if they exceed container widthFixed-count layouts where overflow is clipped or scrolled
WrapItems wrap to the next line with consistent gapTag 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

PropertyTypeDefaultDescription
direction'row' | 'column' | 'row-reverse' | 'column-reverse''column'Main axis direction
gapnumber | string16Space 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)
wrapboolean | 'wrap' | 'nowrap' | 'wrap-reverse'falseWhether children wrap to new lines
inlinebooleanfalseRenders as inline-flex instead of flex
dividerReactNode—Element inserted between each child (e.g., a Divider)
asElementType'div'Polymorphic element override (<ul>, <nav>, <section>, etc.)
childrenReactNode—Child elements to stack
classNamestring—Custom CSS class
styleCSSProperties—Inline style overrides

CSS Custom Properties

Custom PropertyDescriptionDefault
--stack-gapGap between children16px
--stack-directionFlex directioncolumn
--stack-alignCross-axis alignmentstretch
--stack-justifyMain-axis distributionflex-start

Token Mappings

Design Token Mappings

TokenRoleTypical Value
space.0No gap0
space.1Tight gap4px
space.2Small gap8px
space.3Compact gap12px
space.4Default gap16px
space.5Medium gap20px
space.6Relaxed gap24px
space.8Loose gap32px
space.10Extra-loose gap40px
space.12Section gap48px
space.16Page section gap64px
color.border.subtleDivider 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:

StateTriggerEffect
DefaultInitial renderStack renders with specified direction, gap, and alignment
Responsive directionViewport crosses breakpointDirection changes (e.g., row on desktop → column on mobile)
WrappingChildren exceed container widthItems wrap to new lines maintaining consistent gap
EmptyNo childrenStack renders as an empty container with no visible output
Single childOne child elementStack 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-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.
  • 2.4.3 Focus Order (Level A): When using 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.
  • 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 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:

Contentas valueWhy
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 as prop to render semantically correct HTML (<ul>, <nav>, <section>)
  • Pair Stack with design tokens for consistent spacing (gap={space.4} instead of gap={16})
  • Use the divider prop 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-reverse or row-reverse for content where reading order matters (WCAG 1.3.2)
  • Mix Stack and manual margins — let gap handle 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:

PatternStructureDescription
Form layoutVStack > FormField[]Vertical stack of labelled form fields with consistent vertical spacing
Button groupHStack > Button[]Horizontal row of related action buttons
Card contentVStack > Heading + Text + HStack(buttons)Vertical card layout with nested horizontal button row
Page layoutVStack > Header + Main + FooterFull page vertical structure
Inline metadataHStack > Avatar + VStack(name + date)User display with avatar beside stacked name/date

Code Snippets

html
<!-- 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>
tsx
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.

LayoutVStackHStackFlex Stack