CSS Grid is a two-dimensional layout system — it handles rows and columns simultaneously, in a single coordinated model. That distinction separates it from Flexbox, which operates along a single axis at a time. Understanding where that line falls is the foundation for choosing the right tool, and for using Grid without fighting it.
The Grid Container Model
Declaring display: grid on an element makes it a grid container. Every direct child becomes a grid item. The grid itself is defined by tracks — columns and rows — which you specify on the container.
.layout {
display: grid;
grid-template-columns: 250px 1fr 1fr;
grid-template-rows: auto 1fr auto;
}
Explicit tracks are the ones you declare with grid-template-columns and grid-template-rows. Implicit tracks are generated automatically when grid items overflow the explicit grid — either because you place items outside the declared area or because auto-placement creates more rows than you defined. The size of implicit tracks is controlled by grid-auto-rows and grid-auto-columns.
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: minmax(200px, auto); /* implicit rows get this sizing */
}
This pattern is common for card grids where you know the column count but not the row count.
The fr Unit
The fractional unit (fr) is Grid’s mechanism for proportional sizing. It distributes available space after fixed and intrinsic tracks have been resolved.
Given grid-template-columns: 200px 1fr 2fr, the browser allocates 200px to the first column, then divides the remaining width: one part to the second column, two parts to the third. The fr unit never causes a column to shrink below its minimum content size unless you explicitly allow it.
This is meaningfully different from percentage widths. Percentages are calculated from the parent container width before accounting for gaps or other fixed tracks. fr units are calculated after — making them simpler for most layout work.
repeat(), minmax(), and Responsive Grids
repeat() reduces repetition in track definitions:
grid-template-columns: repeat(4, 1fr);
/* equivalent to: 1fr 1fr 1fr 1fr */
Combined with minmax(), it becomes the basis for intrinsically responsive grids — layouts that reflow without media queries:
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
minmax(280px, 1fr) tells the browser: each column should be at least 280px wide, and expand to fill available space proportionally. auto-fill creates as many columns as fit.
auto-fill vs auto-fit
The difference is subtle but matters when grid items don’t fill the available space:
auto-fillpreserves empty tracks. If three columns fit but only two items exist, the third column track still occupies space.auto-fitcollapses empty tracks to zero width, allowing the existing items to stretch across the full container.
/* auto-fit: two items stretch to fill the row */
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
/* auto-fill: two items sit in two columns; empty column remains */
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
For card grids where you want the last row to stretch, auto-fit is usually the right choice. For layouts where you want alignment across rows, auto-fill keeps the column structure intact.
Named Grid Areas
Grid template areas are one of the most expressive features in CSS Grid — they let you describe your layout visually in CSS, using names instead of line numbers.
.page {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: 64px 1fr 48px;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
}
.site-header { grid-area: header; }
.site-sidebar { grid-area: sidebar; }
.site-main { grid-area: main; }
.site-footer { grid-area: footer; }
Each string in grid-template-areas represents a row. Each word in the string names the cell. Repeated names merge into a single area — so "header header" creates a two-column-wide header spanning both columns. A period (.) creates an unnamed empty cell.
The value of this approach is legibility. The ASCII-art representation of the layout lives directly in the CSS, making it immediately auditable. Restructuring for a different breakpoint means rewriting the template string, not hunting for numeric line references.
@media (max-width: 768px) {
.page {
grid-template-columns: 1fr;
grid-template-rows: auto;
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
}
}
Named areas also implicitly create named lines. Declaring grid-template-areas with an area called sidebar generates lines named sidebar-start and sidebar-end on both axes — usable in explicit placement rules if needed.
Line-Based Placement
Before named areas existed (and alongside them for precise control), Grid items are placed using line numbers. Grid lines are numbered starting at 1 from the start edge of the grid.
.featured {
grid-column: 1 / 3; /* spans from line 1 to line 3 */
grid-row: 1 / 2;
}
Negative line numbers count from the end of the explicit grid: grid-column: 1 / -1 spans the full width regardless of column count. Span notation is also valid:
.wide-card {
grid-column: span 2; /* occupies two column tracks */
}
When you don’t specify placement, the browser’s auto-placement algorithm fills items into available cells in document order. The grid-auto-flow property controls whether auto-placement proceeds row-first (default) or column-first, and whether the dense packing algorithm should backfill gaps when later items are smaller than earlier ones.
Gap
gap (formerly grid-gap) sets the spacing between tracks:
.grid {
gap: 24px; /* same row-gap and column-gap */
gap: 16px 24px; /* row-gap column-gap */
}
Gap applies only between tracks, never at the outer edges. It interacts cleanly with fr sizing because fr units are calculated after gap space is reserved.
Alignment
Grid inherits much of the alignment vocabulary from Flexbox, extended to two axes. There are four primary properties, operating at two levels: item-level (how items fill their cells) and content-level (how the grid itself distributes within the container).
Item Alignment
justify-items— aligns items along the inline (row) axis within their cells. Default:stretch.align-items— aligns items along the block (column) axis within their cells. Default:stretch.
.grid {
justify-items: center; /* items centered horizontally in their cells */
align-items: start; /* items pinned to the top of their cells */
}
Individual items can override these with justify-self and align-self.
Content Alignment
When the grid tracks don’t fill the entire container — for example, when using fixed pixel tracks in a larger container — the track distribution itself becomes alignable:
justify-content— distributes columns along the inline axis (same values as Flexbox:start,end,center,space-between,space-around,space-evenly).align-content— distributes rows along the block axis.
.constrained-grid {
display: grid;
grid-template-columns: repeat(3, 200px); /* fixed tracks */
justify-content: space-between;
align-content: center;
height: 100vh;
}
The MDN documentation on box alignment in Grid covers the full alignment model, including how baseline alignment interacts with Grid items.
Realistic Layout Patterns
Page Shell
The classic application shell — persistent header, sidebar, scrollable main content, footer:
.app {
display: grid;
grid-template-columns: 260px 1fr;
grid-template-rows: 56px 1fr 40px;
grid-template-areas:
"header header"
"nav content"
"footer footer";
height: 100vh;
}
header { grid-area: header; }
nav { grid-area: nav; overflow-y: auto; }
main { grid-area: content; overflow-y: auto; }
footer { grid-area: footer; }
Grid handles this more cleanly than nested flexbox arrangements because the header and footer participation in the two-column structure is explicit rather than implied.
Responsive Card Grid
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
}
No media queries. Cards reflow from one to four columns as the viewport expands. See the HTML semantics article for guidance on appropriate card markup — the layout layer and the semantic layer are independent decisions.
Magazine Layout
A more editorial pattern — a featured article spanning multiple tracks, with secondary items arranged alongside:
.magazine {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: repeat(3, auto);
gap: 16px;
}
.featured {
grid-column: 1 / 4;
grid-row: 1 / 3;
}
.secondary-1 { grid-column: 4 / 7; grid-row: 1; }
.secondary-2 { grid-column: 4 / 7; grid-row: 2; }
.tertiary { grid-column: 1 / -1; grid-row: 3; }
This kind of layout was historically achieved with floats, absolute positioning, and considerable JavaScript. Grid makes the structure declarative — the visual intent is readable in the CSS.
For layouts that involve web fonts loading custom typefaces, the font metrics affect auto row sizing and can cause layout shifts if fonts load after the grid is painted. Grid’s auto tracks reflect actual content dimensions, so font loading timing matters.
Grid vs Flexbox
The practical distinction: use Grid when the layout is two-dimensional — when items need to align across both rows and columns. Use Flexbox when the layout is one-dimensional — a row of navigation items, a button group, a vertical stack of form fields.
They compose well. A page-level Grid defines the shell; Flexbox handles the internal arrangement of items within each grid area. Using Grid for everything or Flexbox for everything both lead to unnecessary complexity.
Some specific signals:
- Grid: page layout, card grids with alignment requirements across rows, any layout where an item spans multiple rows or columns, named-area templates
- Flexbox: navigation bars, button groups, centering a single element, distribution of items along one axis with wrapping as a secondary concern
The CSS Grid Layout specification (W3C, Level 1) and Level 2 (which adds subgrid) are the authoritative references. Subgrid — grid-template-columns: subgrid on a nested grid — allows nested grids to participate in the parent grid’s track sizing, solving the longstanding problem of aligning content across cards that are themselves grid items. Browser support for subgrid reached broad availability in 2023.
Grid’s alignment model, explicit/implicit track distinction, and named-area syntax give you enough precision to handle nearly any two-dimensional layout problem without resorting to positioning hacks or JavaScript measurement. The cost is a model with more surface area than Flexbox — but the surface area maps directly to capabilities, not accidental complexity.



