What Is CSS Grid?
CSS Grid is a two-dimensional layout system. Unlike flexbox (one axis at a time), Grid handles rows AND columns simultaneously. It lets you define a layout structure on the container, then place items into that structure precisely.
.container {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr auto;
}
That gives you a three-column, three-row layout. The middle column and row expand to fill available space.
+----------+------------------+----------+
| 200px | 1fr | 200px | <- auto (content height)
+----------+------------------+----------+
| | | |
| 200px | 1fr | 200px | <- 1fr (fills remaining)
| | | |
+----------+------------------+----------+
| 200px | 1fr | 200px | <- auto (content height)
+----------+------------------+----------+
Grid vs Flexbox: The Short Version
- Flexbox: one direction at a time (row OR column).
- Grid: both directions at once (rows AND columns).
Flexbox is content-driven โ items determine their own sizes. Grid is layout-driven โ the container defines the structure and items fill it.
Grid Container Setup
display: grid vs display: inline-grid
/* Block-level grid โ takes full width */
.container {
display: grid;
}
/* Inline-level grid โ shrinks to content */
.container-inline {
display: inline-grid;
}
In practice, display: grid is used almost exclusively.
grid-template-columns
Defines the number and size of columns.
.grid {
display: grid;
/* Fixed sizes */
grid-template-columns: 100px 200px 100px;
/* Fractional units โ divide available space */
grid-template-columns: 1fr 2fr 1fr;
/* Mixed */
grid-template-columns: 250px 1fr 250px;
/* Repeat shorthand */
grid-template-columns: repeat(4, 1fr);
/* Auto-sized */
grid-template-columns: auto 1fr auto;
/* Min-max */
grid-template-columns: minmax(200px, 1fr) 2fr minmax(200px, 1fr);
}
The fr unit divides the remaining space after fixed-size tracks are allocated:
Container: 1000px
Columns: 200px 1fr 2fr
1. Fixed track: 200px
2. Remaining: 1000 - 200 = 800px
3. 1fr = 800 / 3 = 267px
4. 2fr = 800 * 2/3 = 533px
Result: [200px][267px][533px]
grid-template-rows
Same syntax as columns, but for rows.
.grid {
display: grid;
grid-template-rows: auto 1fr auto; /* header, content, footer */
grid-template-rows: 60px 1fr 40px; /* fixed header/footer */
grid-template-rows: repeat(3, 100px); /* three 100px rows */
}
Rows not explicitly defined will be implicit rows โ sized by grid-auto-rows.
grid-template (Shorthand)
Combines rows and columns:
.grid {
/* rows / columns */
grid-template: auto 1fr auto / 200px 1fr 200px;
}
gap (row-gap and column-gap)
.grid {
display: grid;
gap: 1rem; /* equal row and column gap */
row-gap: 1rem; /* gap between rows */
column-gap: 2rem; /* gap between columns */
gap: 1rem 2rem; /* row-gap column-gap */
}
gap does not add spacing on the outside edges โ only between tracks.
Named Grid Lines
You can name grid lines for clearer placement:
.grid {
display: grid;
grid-template-columns:
[sidebar-start] 250px
[sidebar-end content-start] 1fr
[content-end aside-start] 250px
[aside-end];
}
.sidebar {
grid-column: sidebar-start / sidebar-end;
}
.main {
grid-column: content-start / content-end;
}
Grid Item Placement
grid-column and grid-row
Items are placed using grid line numbers. Lines are numbered starting at 1.
.item {
grid-column: 1 / 3; /* spans from line 1 to line 3 (two columns) */
grid-row: 1 / 2; /* spans from line 1 to line 2 (one row) */
}
Line: 1 2 3 4
| | | |
| Col 1 | Col 2 | Col 3 |
| | | |
grid-column: 1 / 3 spans:
|===============| |
| Col 1 + Col 2 | |
Shorthand with span:
.item {
grid-column: 1 / span 2; /* start at line 1, span 2 columns */
grid-column: span 3; /* span 3 columns from wherever placed */
grid-row: 2 / span 2; /* start at row line 2, span 2 rows */
}
grid-column-start / grid-column-end
Explicit start and end (same as the shorthand above):
.item {
grid-column-start: 1;
grid-column-end: 3;
grid-row-start: 1;
grid-row-end: 4;
}
grid-area (Shorthand)
Combines row and column placement:
.item {
/* row-start / column-start / row-end / column-end */
grid-area: 1 / 1 / 3 / 4;
}
Or used with named areas (covered next).
Negative Line Numbers
Count from the end using negative numbers:
.full-width {
grid-column: 1 / -1; /* spans from first to last line */
}
This is useful for items that should span the entire grid regardless of column count.
Grid Template Areas
Named areas make complex layouts readable.
.layout {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
"sidebar content aside"
"footer footer footer";
gap: 1rem;
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }
+----------+------------------+----------+
| header |
+----------+------------------+----------+
| | | |
| sidebar | content | aside |
| | | |
+----------+------------------+----------+
| footer |
+----------+------------------+----------+
Rules for template areas:
- Each string is a row.
- Names must form rectangles (no L-shapes or T-shapes).
- Use
.for empty cells. - Area names must be valid CSS identifiers.
.grid {
grid-template-areas:
"header header header"
"nav content ." /* dot = empty cell */
"nav footer footer";
}
Responsive Template Areas
Template areas shine in responsive design โ redefine the layout per breakpoint:
.layout {
display: grid;
grid-template-areas:
"header"
"content"
"sidebar"
"footer";
}
@media (min-width: 768px) {
.layout {
grid-template-columns: 250px 1fr;
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
}
}
@media (min-width: 1200px) {
.layout {
grid-template-columns: 250px 1fr 200px;
grid-template-areas:
"header header header"
"sidebar content aside"
"footer footer footer";
}
}
The HTML structure stays the same. Only the CSS changes.
Implicit Grid & Auto Placement
grid-auto-rows and grid-auto-columns
When items land outside the explicit grid (more items than defined rows), they create implicit tracks. Control their size:
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: 100px 100px; /* only 2 explicit rows */
grid-auto-rows: 150px; /* implicit rows = 150px */
grid-auto-rows: minmax(100px, auto); /* at least 100px, grow to fit */
}
grid-auto-flow
Controls how auto-placed items fill the grid:
.grid {
grid-auto-flow: row; /* default: fill rows left to right */
grid-auto-flow: column; /* fill columns top to bottom */
grid-auto-flow: dense; /* fill gaps (may reorder items) */
grid-auto-flow: row dense; /* fill rows, backfill gaps */
}
dense packing reorders items to fill holes left by larger items:
Without dense: With dense:
[A A][B ][ ] [A A][B ][D ]
[C C C ][ ] [C C C ][E ]
[D ][E ][ ] (gaps filled)
Warning: dense changes visual order. Like flexbox order, screen readers still follow DOM order.
repeat(), minmax(), auto-fit, auto-fill
repeat()
Shorthand for repeating track definitions:
/* These are equivalent */
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-columns: repeat(4, 1fr);
/* Repeat patterns */
grid-template-columns: repeat(3, 100px 1fr);
/* produces: 100px 1fr 100px 1fr 100px 1fr */
minmax()
Defines a size range for a track:
grid-template-columns: minmax(200px, 1fr) 2fr;
/* first column: at least 200px, at most 1fr */
grid-template-rows: minmax(100px, auto);
/* row: at least 100px, grows to fit content */
auto-fit vs auto-fill
Both create as many columns as fit, but behave differently when there are fewer items than columns:
/* auto-fill: creates empty tracks for unused space */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
/* auto-fit: collapses empty tracks, items stretch */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
Container: 1000px, 3 items, minmax(200px, 1fr)
auto-fill (5 columns created, 2 empty):
[Item 1][Item 2][Item 3][ ][ ]
200px 200px 200px 200px 200px
auto-fit (3 columns, items stretch):
[ Item 1 ][ Item 2 ][ Item 3 ]
333px 333px 333px
Use auto-fit when you want items to stretch and fill the container. Use auto-fill when you want consistent column widths regardless of item count.
The responsive grid pattern (no media queries):
.responsive-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
This creates a grid where:
- Columns are at least 250px wide.
- As many columns as fit in the container.
- Items stretch to fill remaining space.
- Automatically reflows on resize. No media queries needed.
Alignment in Grid
Grid has the same alignment properties as flexbox, plus more.
Container Alignment (All Items)
.grid {
display: grid;
grid-template-columns: repeat(3, 200px);
/* Align all items within their cells */
justify-items: start; /* horizontal: start of cell */
justify-items: end; /* horizontal: end of cell */
justify-items: center; /* horizontal: center of cell */
justify-items: stretch; /* horizontal: fill cell (default) */
align-items: start; /* vertical: top of cell */
align-items: end; /* vertical: bottom of cell */
align-items: center; /* vertical: center of cell */
align-items: stretch; /* vertical: fill cell (default) */
/* Shorthand: align-items / justify-items */
place-items: center center;
/* Align the grid itself within the container */
justify-content: center; /* horizontal alignment of grid */
align-content: center; /* vertical alignment of grid */
/* Shorthand: align-content / justify-content */
place-content: center center;
}
Item Alignment (Single Item)
.item {
justify-self: center; /* horizontal position within cell */
align-self: end; /* vertical position within cell */
/* Shorthand: align-self / justify-self */
place-self: end center;
}
Alignment Cheat Sheet
Horizontal (inline) Vertical (block)
All items: justify-items align-items
Single item: justify-self align-self
Grid itself: justify-content align-content
Shorthand: place-items = align-items / justify-items
place-self = align-self / justify-self
place-content = align-content / justify-content
Subgrid
Subgrid lets a child grid inherit track sizing from its parent grid. This solves the longstanding problem of aligning nested grid items with outer grid tracks.
.parent {
display: grid;
grid-template-columns: 200px 1fr 200px;
gap: 1rem;
}
.child {
grid-column: 1 / -1; /* span all parent columns */
display: grid;
grid-template-columns: subgrid; /* inherit parent's column sizes */
}
Parent grid:
| 200px | 1fr | 200px |
Child with subgrid:
| 200px | 1fr | 200px | <- same tracks as parent
Without subgrid:
| child defines own columns |
Subgrid Use Case: Card Grid with Aligned Content
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
.card {
display: grid;
grid-template-rows: subgrid; /* inherit row sizing from parent */
grid-row: span 3; /* card spans 3 parent rows */
}
/* Now card title, body, and footer align across all cards */
+----------+ +----------+ +----------+
| Title | | Title | | Title | <- Row 1 (aligned)
+----------+ +----------+ +----------+
| Body | | Body | | Body | <- Row 2 (aligned)
| (long) | | | | (medium) |
+----------+ +----------+ +----------+
| Footer | | Footer | | Footer | <- Row 3 (aligned)
+----------+ +----------+ +----------+
Browser support: subgrid is supported in Chrome 117+, Firefox 71+, Safari 16+, Edge 117+. Check caniuse before using in production.
Real-World Grid Patterns
1. Classic Page Layout
.page {
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: 60px 1fr 40px;
grid-template-areas:
"header header"
"nav main"
"footer footer";
min-height: 100vh;
}
.header { grid-area: header; }
.nav { grid-area: nav; }
.main { grid-area: main; }
.footer { grid-area: footer; }
2. Dashboard Grid
.dashboard {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: auto;
gap: 1rem;
}
.widget-large {
grid-column: span 2;
grid-row: span 2;
}
.widget-wide {
grid-column: span 2;
}
.widget-tall {
grid-row: span 2;
}
+----------+----------+-----+-----+
| | | |
| widget-large | W | W |
| | | |
+----------+----------+-----+-----+
| widget-wide | |
+----------+----------+ widget- |
| W | W | tall |
+----------+----------+-----------+
3. Responsive Card Grid (No Media Queries)
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
}
This is one of the most powerful CSS Grid patterns. Cards are at least 280px, expand to fill space, and automatically reflow.
4. Image Gallery with Featured Image
.gallery {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(3, 200px);
gap: 0.5rem;
}
.featured {
grid-column: 1 / 3;
grid-row: 1 / 3;
}
.gallery img {
width: 100%;
height: 100%;
object-fit: cover;
}
+------------------+--------+--------+
| | img | img |
| featured +--------+--------+
| | img | img |
+--------+---------+--------+--------+
| img | img | img | img |
+--------+---------+--------+--------+
5. Magazine Layout
.magazine {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 1rem;
}
.headline {
grid-column: 1 / 5;
grid-row: 1 / 3;
}
.sidebar-story {
grid-column: 5 / 7;
}
.story {
grid-column: span 2;
}
.ad-banner {
grid-column: 1 / -1;
}
6. Pricing Table
.pricing {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
align-items: start;
}
.plan {
display: grid;
grid-template-rows: auto 1fr auto;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.plan-header { padding: 1.5rem; background: #f5f5f5; }
.plan-body { padding: 1.5rem; }
.plan-footer { padding: 1.5rem; }
7. Content Grid with Breakout Sections
.content-grid {
display: grid;
grid-template-columns:
[full-start] 1fr
[content-start] minmax(0, 65ch)
[content-end] 1fr
[full-end];
}
.content-grid > * {
grid-column: content;
}
.content-grid > .full-width {
grid-column: full;
}
This creates a centered content column with the ability for certain elements to break out to full width โ common in blog layouts.
8. Responsive Sidebar
.app {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.app {
grid-template-columns: minmax(200px, 250px) 1fr;
}
}
9. Form Layout
.form-grid {
display: grid;
grid-template-columns: auto 1fr;
gap: 0.75rem 1rem;
align-items: center;
}
.form-grid label {
text-align: right;
}
.form-grid .full-width {
grid-column: 1 / -1;
}
Name: [________________]
Email: [________________]
Message: [________________]
[________________]
[ Submit ] <- full-width
10. Overlapping Elements
Grid items can overlap by placing them in the same cells:
.hero {
display: grid;
grid-template: 1fr / 1fr;
}
.hero > * {
grid-area: 1 / 1; /* all items in same cell */
}
.hero-image {
z-index: 1;
}
.hero-overlay {
z-index: 2;
background: rgba(0, 0, 0, 0.5);
}
.hero-text {
z-index: 3;
place-self: center;
color: white;
}
Grid vs Flexbox Decision Tree
Start here:
|
v
Is the layout 1D (single row or column)?
|
Yes --> Use Flexbox
|
No --> Is the layout 2D (rows AND columns)?
|
Yes --> Use Grid
|
No --> Do items need to align on both axes?
|
Yes --> Use Grid
|
No --> Does content dictate sizing?
|
Yes --> Flexbox (content-driven)
|
No --> Grid (layout-driven)
Side-by-side comparison:
| Scenario | Flexbox | Grid |
|---|---|---|
| Navigation bar | Best choice | Works but overkill |
| Card row (single) | Best choice | Works |
| Card grid (multiple rows) | Works with wrap | Best choice |
| Page layout (header/sidebar/main) | Possible | Best choice |
| Centering one element | Best choice | Works |
| Form layout | Possible | Best choice |
| Dashboard widgets | Difficult | Best choice |
| Toolbar / button group | Best choice | Works |
Responsive Grid Patterns
Mobile-First Grid
/* Mobile: single column */
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
/* Tablet: two columns */
@media (min-width: 768px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Desktop: four columns */
@media (min-width: 1200px) {
.grid {
grid-template-columns: repeat(4, 1fr);
}
}
Fluid Grid (No Media Queries)
.fluid-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
gap: 1rem;
}
The min(250px, 100%) prevents overflow on very narrow screens where 250px exceeds the container width.
Container-Query-Aware Grid
.card-container {
container-type: inline-size;
}
@container (min-width: 600px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@container (min-width: 900px) {
.card-grid {
grid-template-columns: repeat(3, 1fr);
}
}
Debugging CSS Grid
Browser DevTools
All major browsers have grid inspectors:
- Chrome/Edge: Inspect element, check the "Grid" overlay in Layout tab. Shows line numbers, area names, track sizes.
- Firefox: Best grid inspector. Color-coded overlays, shows all grid lines, named areas, and gaps.
- Safari: Inspect element, Layout tab shows grid overlay.
Common Grid Issues
Problem: Items overflowing the grid
-> Check if you're using fr units (they divide available space)
-> Use minmax(0, 1fr) to allow columns to shrink below content size
-> Check for fixed-width content inside grid items
Problem: Grid not taking full height
-> Set min-height: 100vh on the grid container
-> Or height: 100% with parent also having height set
Problem: Gap not showing
-> Verify display: grid is set
-> Check if items span multiple tracks (gap only between tracks)
Problem: Items not aligning in named areas
-> Check spelling of area names
-> Verify areas form valid rectangles
-> Each area name must be a single connected rectangle
Problem: auto-fit not working as expected
-> Needs minmax() โ not just a fixed size
-> repeat(auto-fit, 200px) doesn't stretch items
-> repeat(auto-fit, minmax(200px, 1fr)) does
Advanced Techniques
Dense Packing for Galleries
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
grid-auto-rows: 150px;
grid-auto-flow: dense;
gap: 4px;
}
.gallery .wide { grid-column: span 2; }
.gallery .tall { grid-row: span 2; }
.gallery .large { grid-column: span 2; grid-row: span 2; }
Grid with CSS Variables
.grid {
--columns: 3;
--gap: 1rem;
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
gap: var(--gap);
}
@media (max-width: 768px) {
.grid { --columns: 1; }
}
@media (min-width: 769px) and (max-width: 1024px) {
.grid { --columns: 2; }
}
Nesting Grids
.outer-grid {
display: grid;
grid-template-columns: 1fr 3fr;
gap: 2rem;
}
.inner-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
Full-Bleed Layout
.wrapper {
display: grid;
grid-template-columns:
1fr
min(65ch, 100%)
1fr;
}
.wrapper > * {
grid-column: 2;
}
.wrapper > .full-bleed {
grid-column: 1 / -1;
}
Performance Notes
CSS Grid performs well in modern browsers. A few tips:
- Avoid animating grid track sizes โ changing
grid-template-columnstriggers layout recalculation. Animate transforms or opacity instead. - Use
content-visibility: autoon off-screen grid items for large grids โ lets the browser skip rendering until items scroll into view. auto-fit/auto-fillwith many items is efficient โ the browser optimizes track computation.- Explicit grids are faster than implicit โ define
grid-template-rowswhen you know the row count instead of relying ongrid-auto-rows. - Subgrid adds minimal overhead โ it inherits tracks rather than computing new ones.
Browser Support
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| Basic Grid | 57+ | 52+ | 10.1+ | 16+ |
| gap | 66+ | 61+ | 12+ | 16+ |
| subgrid | 117+ | 71+ | 16+ | 117+ |
| Masonry (experimental) | Flag only | Flag only | No | Flag only |
Grid is safe for production. The only newer feature requiring caution is subgrid โ check your target browser requirements.
Quick Reference
| Property | Applies To | Common Values |
|---|---|---|
| display | container | grid, inline-grid |
| grid-template-columns | container | track sizes, repeat(), minmax() |
| grid-template-rows | container | track sizes, repeat(), minmax() |
| grid-template-areas | container | named area strings |
| gap | container | length values |
| grid-auto-rows | container | track sizes |
| grid-auto-columns | container | track sizes |
| grid-auto-flow | container | row, column, dense |
| justify-items | container | start, end, center, stretch |
| align-items | container | start, end, center, stretch |
| justify-content | container | start, end, center, space-between, etc. |
| align-content | container | start, end, center, space-between, etc. |
| grid-column | item | start / end, span N |
| grid-row | item | start / end, span N |
| grid-area | item | name, or row-start / col-start / row-end / col-end |
| justify-self | item | start, end, center, stretch |
| align-self | item | start, end, center, stretch |
Key Takeaways
- CSS Grid is the right tool for two-dimensional layouts.
grid-template-columnsandgrid-template-rowsdefine the structure.frunits divide remaining space proportionally.repeat(auto-fit, minmax(250px, 1fr))is the magic formula for responsive grids without media queries.- Named template areas make complex layouts readable and easy to restructure.
subgridinherits parent grid tracks โ essential for aligning nested content.auto-fitcollapses empty tracks (items stretch).auto-fillkeeps empty tracks (items don't stretch).- Grid and flexbox are complementary. Use grid for the overall page structure, flexbox for component internals.
- Use negative line numbers (
1 / -1) to span the entire grid. - Browser support is excellent โ safe for all modern browsers.