Loading…
Loading…
A two-dimensional layout system for organizing content in rows and columns.
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:
When NOT to use a Grid:
order and wrapping may be more intuitiveExperiment with column counts, gap values, and responsive breakpoints using the Grid Generator. Fine-tune your gap and padding values with the Spacing Scale Generator.
| Variant | Description | CSS Technique |
|---|---|---|
| Fixed columns | A set number of columns at every viewport size. | grid-template-columns: repeat(3, 1fr) |
| Responsive auto-fill | Columns automatically created to fill available space. Items wrap to new rows. | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)) |
| Responsive auto-fit | Like auto-fill but collapses empty tracks, stretching items to fill. | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) |
| Asymmetric | Columns with different widths (e.g., sidebar + content). | grid-template-columns: 240px 1fr or grid-template-columns: 1fr 2fr 1fr |
| Named areas | Columns and rows defined by named template areas for readable layouts. | grid-template-areas: "header header" "sidebar main" "footer footer" |
| Masonry | Items fill vertical space optimally (Pinterest-style). | grid-template-rows: masonry (experimental) or JS-based |
| Subgrid | Child grid inherits parent's track sizing. | grid-template-columns: subgrid |
This is the most commonly confused Grid distinction:
| Behavior | auto-fill | auto-fit |
|---|---|---|
| Extra space | Creates empty tracks, items stay at minmax size | Collapses empty tracks, items stretch to fill |
| Few items | Items stay small, empty columns visible | Items stretch to fill the row |
| Many items | Identical behavior | Identical behavior |
| Best for | Uniform sizing regardless of item count | Filling available space when item count varies |
| Gap Size | Value | Use Case |
|---|---|---|
| Tight | 8px | Dense dashboards, thumbnail grids |
| Default | 16px | Standard card grids, form layouts |
| Relaxed | 24px | Marketing pages, content-heavy grids |
| Loose | 32–48px | Hero-level layouts, spacious designs |
Preview these gap values in context with the Spacing Scale Generator.
| Property | Type | Default | Description |
|---|---|---|---|
columns | number | string | 12 | Number of columns or a CSS grid-template-columns value |
rows | number | string | 'auto' | Number of rows or a CSS grid-template-rows value |
gap | number | string | 16 | Uniform gap between rows and columns (px or token) |
columnGap | number | string | — | Override gap for columns only |
rowGap | number | string | — | Override gap for rows only |
areas | string[] | — | Array of template area strings (each string is one row) |
autoFlow | 'row' | 'column' | 'dense' | 'row dense' | 'column dense' | 'row' | Controls auto-placement algorithm direction |
autoColumns | string | — | Size for implicitly created columns |
autoRows | string | — | 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 |
alignContent | string | — | Alignment of the entire grid within its container (vertical) |
justifyContent | string | — | Alignment of the entire grid within its container (horizontal) |
minChildWidth | string | — | Shorthand: generates repeat(auto-fit, minmax(value, 1fr)) |
inline | boolean | false | Renders as inline-grid instead of grid |
| Property | Type | Default | Description |
|---|---|---|---|
colSpan | number | 1 | Number of columns the item spans |
rowSpan | number | 1 | Number of rows the item spans |
colStart | number | — | Starting column line |
colEnd | number | — | Ending column line |
rowStart | number | — | Starting row line |
rowEnd | number | — | Ending row line |
area | string | — | Named grid area this item occupies |
alignSelf | string | — | Override vertical alignment for this item |
justifySelf | string | — | Override horizontal alignment for this item |
| Token | Role | Typical Value |
|---|---|---|
space.1 | Tight gap | 4px |
space.2 | Small gap | 8px |
space.3 | Compact gap | 12px |
space.4 | Default gap | 16px |
space.6 | Relaxed gap | 24px |
space.8 | Loose gap | 32px |
space.12 | Spacious gap | 48px |
breakpoint.sm | Small viewport threshold | 640px |
breakpoint.md | Medium viewport threshold | 768px |
breakpoint.lg | Large viewport threshold | 1024px |
breakpoint.xl | Extra-large viewport threshold | 1280px |
size.container.sm | Narrow content width | 640px |
size.container.md | Default content width | 768px |
size.container.lg | Wide content width | 1024px |
size.container.xl | Full content width | 1280px |
size.sidebar | Sidebar width in asymmetric grids | 240–280px |
Visualise these values using the Spacing Scale Generator and experiment with column configurations in the Grid Generator.
Grid is a layout primitive with no interactive states of its own. However, responsive behavior constitutes a form of state driven by viewport conditions:
| State | Trigger | Effect |
|---|---|---|
| Default | Initial render | Grid renders with specified columns, gap, and areas |
| Responsive breakpoint | Viewport crosses a breakpoint threshold | Column count, gap, and/or areas change. Items reflow. |
| Dense packing | autoFlow: 'dense' | Auto-placement algorithm fills gaps by reordering items visually (caution: creates a11y issues) |
| Overflow | Content exceeds grid area | Items overflow their cells. Handled by item-level overflow styles, not the grid. |
| Subgrid inheritance | Child uses subgrid | Child grid tracks align with parent tracks |
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.
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:
<nav>, <main>, <aside>), and lists within grid cells to express structure programmatically.grid-column, grid-row, or order to visually reorder content in ways that contradict the reading sequence.auto-fit/auto-fill or media queries. Verify with the Grid Generator.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.
Do:
auto-fit or auto-fill with minmax() for responsive card grids that adapt without media queriesfr units for flexible tracks and reserve pixel/rem values for fixed elements (sidebars, gutters)min(100%, <desired-width>) pattern inside minmax() to prevent overflow on narrow viewportscontainer queries for component-level responsive grids that adapt to their container, not just the viewportDon't:
grid-auto-flow: dense in content where reading order matters — it reorders items visually without changing the DOMauto or min-contentResponsive Strategy:
| Viewport | Columns | Gap | Pattern |
|---|---|---|---|
| < 640px | 1 | 16px | Single column stack |
| 640–1023px | 2 | 16–24px | Compact grid |
| 1024–1279px | 3 | 24px | Standard grid |
| ≥ 1280px | 4+ | 24–32px | Full grid |
<!-- 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>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>
);
}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.