Skip to content

Grid

A two-dimensional layout system for organizing content in rows and columns.

  • Grid Layout
  • CSS Grid

Overview

The Grid is a two-dimensional layout system that organizes content into rows and columns simultaneously. Unlike Stack (which operates on a single axis using Flexbox), Grid leverages CSS Grid Layout to provide precise control over both horizontal and vertical placement, spanning, alignment, and sizing — all declaratively.

Grid is the backbone of page-level layout and complex component arrangements. It replaces the float-based and framework-specific 12-column grids that dominated web development for over a decade with a browser-native, infinitely flexible system.

When to use a Grid:

  • For page-level layout (header, sidebar, main content, footer)
  • For card grids that need uniform sizing across rows and columns
  • For dashboard layouts with widgets of varying sizes
  • For form layouts where labels and inputs need column alignment
  • For any layout requiring both row and column control simultaneously

When NOT to use a Grid:

  • For single-axis stacking — use a Stack with Flexbox
  • For simple centering — Flexbox is simpler
  • For flowing inline content — use normal document flow
  • For layout that should reorder based on content priority — Flexbox's order and wrapping may be more intuitive

Experiment with column counts, gap values, and responsive breakpoints using the Grid Generator. Fine-tune your gap and padding values with the Spacing Scale Generator.

Variants

Grid Variants

VariantDescriptionCSS Technique
Fixed columnsA set number of columns at every viewport size.grid-template-columns: repeat(3, 1fr)
Responsive auto-fillColumns automatically created to fill available space. Items wrap to new rows.grid-template-columns: repeat(auto-fill, minmax(250px, 1fr))
Responsive auto-fitLike auto-fill but collapses empty tracks, stretching items to fill.grid-template-columns: repeat(auto-fit, minmax(250px, 1fr))
AsymmetricColumns with different widths (e.g., sidebar + content).grid-template-columns: 240px 1fr or grid-template-columns: 1fr 2fr 1fr
Named areasColumns and rows defined by named template areas for readable layouts.grid-template-areas: "header header" "sidebar main" "footer footer"
MasonryItems fill vertical space optimally (Pinterest-style).grid-template-rows: masonry (experimental) or JS-based
SubgridChild grid inherits parent's track sizing.grid-template-columns: subgrid

auto-fill vs auto-fit

This is the most commonly confused Grid distinction:

Behaviorauto-fillauto-fit
Extra spaceCreates empty tracks, items stay at minmax sizeCollapses empty tracks, items stretch to fill
Few itemsItems stay small, empty columns visibleItems stretch to fill the row
Many itemsIdentical behaviorIdentical behavior
Best forUniform sizing regardless of item countFilling available space when item count varies

Gap Variants

Gap SizeValueUse Case
Tight8pxDense dashboards, thumbnail grids
Default16pxStandard card grids, form layouts
Relaxed24pxMarketing pages, content-heavy grids
Loose32–48pxHero-level layouts, spacious designs

Preview these gap values in context with the Spacing Scale Generator.

Properties

Grid Properties

PropertyTypeDefaultDescription
columnsnumber | string12Number of columns or a CSS grid-template-columns value
rowsnumber | string'auto'Number of rows or a CSS grid-template-rows value
gapnumber | string16Uniform gap between rows and columns (px or token)
columnGapnumber | string—Override gap for columns only
rowGapnumber | string—Override gap for rows only
areasstring[]—Array of template area strings (each string is one row)
autoFlow'row' | 'column' | 'dense' | 'row dense' | 'column dense''row'Controls auto-placement algorithm direction
autoColumnsstring—Size for implicitly created columns
autoRowsstring—Size for implicitly created rows
alignItems'start' | 'end' | 'center' | 'stretch' | 'baseline''stretch'Vertical alignment of items within their grid area
justifyItems'start' | 'end' | 'center' | 'stretch''stretch'Horizontal alignment of items within their grid area
alignContentstring—Alignment of the entire grid within its container (vertical)
justifyContentstring—Alignment of the entire grid within its container (horizontal)
minChildWidthstring—Shorthand: generates repeat(auto-fit, minmax(value, 1fr))
inlinebooleanfalseRenders as inline-grid instead of grid

Grid Item Properties

PropertyTypeDefaultDescription
colSpannumber1Number of columns the item spans
rowSpannumber1Number of rows the item spans
colStartnumber—Starting column line
colEndnumber—Ending column line
rowStartnumber—Starting row line
rowEndnumber—Ending row line
areastring—Named grid area this item occupies
alignSelfstring—Override vertical alignment for this item
justifySelfstring—Override horizontal alignment for this item

Token Mappings

Design Token Mappings

TokenRoleTypical Value
space.1Tight gap4px
space.2Small gap8px
space.3Compact gap12px
space.4Default gap16px
space.6Relaxed gap24px
space.8Loose gap32px
space.12Spacious gap48px
breakpoint.smSmall viewport threshold640px
breakpoint.mdMedium viewport threshold768px
breakpoint.lgLarge viewport threshold1024px
breakpoint.xlExtra-large viewport threshold1280px
size.container.smNarrow content width640px
size.container.mdDefault content width768px
size.container.lgWide content width1024px
size.container.xlFull content width1280px
size.sidebarSidebar width in asymmetric grids240–280px

Visualise these values using the Spacing Scale Generator and experiment with column configurations in the Grid Generator.

States

Grid States

Grid is a layout primitive with no interactive states of its own. However, responsive behavior constitutes a form of state driven by viewport conditions:

StateTriggerEffect
DefaultInitial renderGrid renders with specified columns, gap, and areas
Responsive breakpointViewport crosses a breakpoint thresholdColumn count, gap, and/or areas change. Items reflow.
Dense packingautoFlow: 'dense'Auto-placement algorithm fills gaps by reordering items visually (caution: creates a11y issues)
OverflowContent exceeds grid areaItems overflow their cells. Handled by item-level overflow styles, not the grid.
Subgrid inheritanceChild uses subgridChild grid tracks align with parent tracks

Responsive Patterns

The most common responsive pattern transforms a multi-column grid into a single-column stack on mobile:

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: var(--space-4);
}

The min(100%, 300px) pattern prevents horizontal overflow on viewports narrower than the minimum item width — a critical detail many implementations miss.

Important a11y note: grid-auto-flow: dense and explicit order changes can cause visual order to diverge from DOM order. Screen readers and keyboard users follow DOM order. Per WCAG 2.4.3 (Focus Order) and 1.3.2 (Meaningful Sequence), visual reordering must not create a confusing navigation experience.

Accessibility

Accessibility Requirements

Grid is a CSS layout mechanism, not a semantic structure. The grid container and items carry no implicit ARIA semantics — accessibility depends entirely on the semantic HTML placed within grid cells.

WCAG Success Criteria:

  • 1.3.1 Info and Relationships (Level A): Grid visual structure does not convey semantic relationships. Use proper headings, landmarks (<nav>, <main>, <aside>), and lists within grid cells to express structure programmatically.
  • 1.3.2 Meaningful Sequence (Level A): The DOM order must make sense when read linearly, regardless of visual placement. Avoid using grid-column, grid-row, or order to visually reorder content in ways that contradict the reading sequence.
  • 2.4.3 Focus Order (Level A): Tab order follows DOM order, not visual order. If you visually place a sidebar before main content using Grid but the sidebar appears after main content in the DOM, keyboard navigation will seem to jump randomly. Always match DOM order to intended reading order.
  • 1.4.10 Reflow (Level AA): At 320px viewport width (or 400% zoom), content must reflow into a single column without horizontal scrolling. Grid layouts must gracefully degrade using auto-fit/auto-fill or media queries. Verify with the Grid Generator.
  • 1.4.4 Resize Text (Level AA): Text resized up to 200% must not be clipped or overlapped by fixed grid track sizes. Use fr units and minmax() instead of fixed pixel widths for content tracks.

Landmark Usage in Grid Layouts:

<div class="page-grid">
  <header>...</header>
  <nav aria-label="Main">...</nav>
  <main>...</main>
  <aside aria-label="Related">...</aside>
  <footer>...</footer>
</div>

The grid provides visual positioning; the HTML5 elements provide semantic structure. Never rely on Grid alone to communicate layout meaning. Validate your divider and separator contrast ratios with the Contrast Checker.

Usage Guidelines

Usage Guidelines

Do:

  • Use auto-fit or auto-fill with minmax() for responsive card grids that adapt without media queries
  • Use named grid areas for complex page layouts — they are self-documenting and easier to maintain
  • Use fr units for flexible tracks and reserve pixel/rem values for fixed elements (sidebars, gutters)
  • Ensure DOM order matches visual reading order to maintain accessible navigation (WCAG 1.3.2, 2.4.3)
  • Use the min(100%, <desired-width>) pattern inside minmax() to prevent overflow on narrow viewports
  • Combine with CSS container queries for component-level responsive grids that adapt to their container, not just the viewport

Don't:

  • Use CSS Grid for simple single-axis layouts — a Stack with Flexbox is simpler and more semantic
  • Use grid-auto-flow: dense in content where reading order matters — it reorders items visually without changing the DOM
  • Set fixed pixel heights on grid rows containing dynamic content — use auto or min-content
  • Create grid layouts that require horizontal scrolling at any common viewport width (WCAG 1.4.10)
  • Mix Grid and Flexbox on the same container — choose one layout mode per element
  • Forget to test with keyboard navigation when using visual reordering — focus order must remain logical

Responsive Strategy:

ViewportColumnsGapPattern
< 640px116pxSingle column stack
640–1023px216–24pxCompact grid
1024–1279px324pxStandard grid
≥ 1280px4+24–32pxFull grid

Code Snippets

html
<!-- Responsive auto-fit grid -->
<div class="grid" style="
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  gap: 24px;
">
  <div class="card">Card 1</div>
  <div class="card">Card 2</div>
  <div class="card">Card 3</div>
  <div class="card">Card 4</div>
</div>

<!-- Page layout with named areas -->
<div class="page-grid" style="
  display: grid;
  grid-template-areas:
    'header header'
    'sidebar main'
    'footer footer';
  grid-template-columns: 260px 1fr;
  grid-template-rows: auto 1fr auto;
  min-height: 100vh;
  gap: 0;
">
  <header style="grid-area: header;">Header</header>
  <nav style="grid-area: sidebar;" aria-label="Main navigation">Sidebar</nav>
  <main style="grid-area: main;">Content</main>
  <footer style="grid-area: footer;">Footer</footer>
</div>

<!-- Dashboard grid with spanning items -->
<div class="dashboard-grid" style="
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
">
  <div style="grid-column: span 2; grid-row: span 2;">Large widget</div>
  <div>Small widget 1</div>
  <div>Small widget 2</div>
  <div>Small widget 3</div>
  <div>Small widget 4</div>
</div>

<!-- Responsive: collapse sidebar on mobile -->
<style>
@media (max-width: 768px) {
  .page-grid {
    grid-template-areas:
      'header'
      'main'
      'footer' !important;
    grid-template-columns: 1fr !important;
  }
  .page-grid nav { display: none; }
}
</style>
tsx
import React from 'react';

interface GridProps {
  columns?: number | string;
  rows?: string;
  gap?: number | string;
  columnGap?: number | string;
  rowGap?: number | string;
  areas?: string[];
  autoFlow?: 'row' | 'column' | 'dense' | 'row dense' | 'column dense';
  autoRows?: string;
  autoColumns?: string;
  alignItems?: 'start' | 'end' | 'center' | 'stretch' | 'baseline';
  justifyItems?: 'start' | 'end' | 'center' | 'stretch';
  minChildWidth?: string;
  inline?: boolean;
  children: React.ReactNode;
  className?: string;
  style?: React.CSSProperties;
}

function Grid({
  columns = 12,
  rows,
  gap = 16,
  columnGap,
  rowGap,
  areas,
  autoFlow,
  autoRows,
  autoColumns,
  alignItems,
  justifyItems,
  minChildWidth,
  inline = false,
  children,
  className,
  style,
}: GridProps) {
  const templateColumns = minChildWidth
    ? `repeat(auto-fit, minmax(min(100%, ${minChildWidth}), 1fr))`
    : typeof columns === 'number'
      ? `repeat(${columns}, 1fr)`
      : columns;

  return (
    <div
      className={className}
      style={{
        display: inline ? 'inline-grid' : 'grid',
        gridTemplateColumns: templateColumns,
        gridTemplateRows: rows,
        gridTemplateAreas: areas?.map(a => `"${a}"`).join(' '),
        gap: typeof gap === 'number' ? `${gap}px` : gap,
        columnGap: columnGap != null ? (typeof columnGap === 'number' ? `${columnGap}px` : columnGap) : undefined,
        rowGap: rowGap != null ? (typeof rowGap === 'number' ? `${rowGap}px` : rowGap) : undefined,
        gridAutoFlow: autoFlow,
        gridAutoRows: autoRows,
        gridAutoColumns: autoColumns,
        alignItems,
        justifyItems,
        ...style,
      }}
    >
      {children}
    </div>
  );
}

interface GridItemProps {
  colSpan?: number;
  rowSpan?: number;
  colStart?: number;
  rowStart?: number;
  area?: string;
  children: React.ReactNode;
  className?: string;
}

function GridItem({ colSpan, rowSpan, colStart, rowStart, area, children, className }: GridItemProps) {
  return (
    <div
      className={className}
      style={{
        gridColumn: colSpan ? `span ${colSpan}` : undefined,
        gridRow: rowSpan ? `span ${rowSpan}` : undefined,
        gridColumnStart: colStart,
        gridRowStart: rowStart,
        gridArea: area,
      }}
    >
      {children}
    </div>
  );
}

// Usage: Dashboard
function Dashboard() {
  return (
    <Grid columns={4} gap={16}>
      <GridItem colSpan={2} rowSpan={2}>
        <div className="widget widget--large">Revenue Chart</div>
      </GridItem>
      <GridItem><div className="widget">Users Online</div></GridItem>
      <GridItem><div className="widget">Conversion Rate</div></GridItem>
      <GridItem colSpan={2}><div className="widget">Recent Orders</div></GridItem>
    </Grid>
  );
}

// Usage: Responsive card grid
function CardGrid({ cards }: { cards: { id: string; title: string }[] }) {
  return (
    <Grid minChildWidth="280px" gap={24}>
      {cards.map(card => (
        <div key={card.id} className="card">{card.title}</div>
      ))}
    </Grid>
  );
}

Design Systems

Design System Implementations

Material Design 3 (MUI) provides <Grid> (v1, based on Flexbox with a 12-column model) and the newer <Grid2> (also Flexbox-based). MUI does not expose a CSS Grid component natively — its Grid is a Flexbox wrapper with container/item props, xs/sm/md/lg/xl breakpoint sizing, spacing (gap), and direction. For true CSS Grid, MUI users typically use the <Box> component with display="grid" and pass grid properties directly. MUI's Grid2 (unstable) removes the container/item distinction, using size and offset props instead.

Ant Design provides a <Row>/<Col> grid system modeled after Bootstrap's 24-column grid. Props include gutter (gap, supports responsive object { xs: 8, md: 16 }), justify, align, wrap, and per-Col props span, offset, push, pull, and responsive breakpoints (xs, sm, md, lg, xl, xxl). This is Flexbox-based, not CSS Grid.

Chakra UI provides <Grid> (CSS Grid) and <SimpleGrid> (convenience wrapper). Grid accepts templateColumns, templateRows, gap, autoFlow, autoRows, autoColumns, and all grid CSS properties via style props. SimpleGrid offers columns (fixed column count) and minChildWidth (generates auto-fit/minmax). GridItem accepts colSpan, rowSpan, colStart, colEnd, rowStart, rowEnd, area. Chakra's grid is the most CSS Grid-native implementation in mainstream libraries.

Radix UI does not provide a grid component — layout is considered application-level rather than a headless primitive concern.

Headless UI does not include layout primitives.

Tailwind CSS provides grid utilities directly: grid, grid-cols-{n}, gap-{n}, col-span-{n}, row-span-{n}, auto-cols-{size}, auto-rows-{size}, and responsive variants. Combined with @apply or component abstractions, Tailwind's utility approach is the most flexible for custom grids. The utility grid-cols-[repeat(auto-fit,minmax(280px,1fr))] enables responsive grids without media queries.

LayoutGrid LayoutCSS Grid