CSS Grid Tutorial: Build a Responsive Layout Step by Step

- Build a Responsive Grid Layout in Under 20 Lines of CSS
- Step 1: Turn an Element Into a Grid
- Step 2: Use the fr Unit Instead of Fixed Widths
- Step 3: Add Gaps Instead of Margins
- Step 4: Make It Responsive Without Media Queries
- Step 5: Place Items Precisely With Line Numbers
- Step 6: Name Regions With grid-template-areas
- Aligning Items Inside the Grid
- Implicit vs Explicit Grids
- A Complete Responsive Example
- CSS Grid vs Flexbox: Which to Reach For
- Common Grid Mistakes to Avoid
- Where to Go Next
Build a Responsive Grid Layout in Under 20 Lines of CSS
CSS Grid is a two-dimensional layout system: it controls rows and columns at the same time, which is exactly what page layouts need. Here is a complete, responsive grid you can paste into any project right now:
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
That is the whole trick for a card layout. Every direct child of .grid becomes a grid item and flows into columns that are at least 250px wide, growing to fill the space and wrapping to new rows automatically — no media queries required. The rest of this tutorial explains why each line does what it does, then builds a full page layout on top of it.
CSS Grid works in every modern browser (Chrome, Firefox, Safari, and Edge have supported it since 2017), so you can use everything below in production today.
Step 1: Turn an Element Into a Grid
Grid starts with one declaration on the parent, called the grid container. Its direct children become grid items automatically.
.container {
display: grid;
}
By itself, display: grid stacks items in a single column. The layout begins once you define tracks — the columns and rows.
The two properties that define tracks
.container {
display: grid;
grid-template-columns: 200px 200px 200px;
grid-template-rows: 100px 100px;
}
This creates a fixed 3-column, 2-row grid. grid-template-columns lists the width of each column left to right; grid-template-rows lists the height of each row top to bottom. The number of values equals the number of tracks.
Step 2: Use the fr Unit Instead of Fixed Widths
Fixed pixel widths break on small screens. The fr unit ("fraction") solves this by splitting the available space into shares.
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 1rem;
}
Here 1fr 1fr 1fr means "three equal columns." Change the ratio and the split changes with it: 2fr 1fr gives a wide column beside a narrow one, always at a 2:1 ratio no matter the viewport width.
fr also plays well with fixed values. A common sidebar layout is one fixed rail and one flexible main area:
.layout {
display: grid;
grid-template-columns: 250px 1fr;
gap: 2rem;
}
The sidebar stays 250px; the main column absorbs whatever remains.
repeat() keeps long track lists readable
Typing 1fr 1fr 1fr 1fr gets old fast. repeat() is shorthand:
grid-template-columns: repeat(4, 1fr); /* four equal columns */
grid-template-columns: repeat(2, 200px 1fr); /* 200px 1fr 200px 1fr */
Step 3: Add Gaps Instead of Margins
Spacing between grid items uses the gap property, not margins. Margins on grid items create uneven, hard-to-manage gutters; gap applies space between tracks only, never on the outer edge.
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem; /* row and column gap */
/* or set them separately: */
row-gap: 1rem;
column-gap: 2rem;
}
gap is one of Grid's best features — you get clean, consistent gutters with a single line, and it works in Flexbox too.
Step 4: Make It Responsive Without Media Queries
This is where Grid outshines older techniques. The combination of auto-fit, minmax(), and repeat() builds a layout that reflows on its own.
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
Read it inside-out:
minmax(250px, 1fr)— each column is never narrower than250pxand never wider than an equal share of free space.auto-fit— fit as many250px+ columns as the container allows, then stretch them to fill the row.repeat()— apply that rule to every column it can create.
On a 1200px screen you get four or five columns; on a phone you get one — all from a single declaration. This pattern is the fastest way to build a responsive card grid, image gallery, or product listing.
auto-fit vs auto-fill
These two keywords look identical but differ when items are few:
| Keyword | Behavior when items don't fill the row |
|---|---|
auto-fit |
Empty tracks collapse; existing items stretch to fill the row |
auto-fill |
Empty tracks are kept at their minimum width; items stay their size |
Use auto-fit when you want items to grow and fill the space (most card layouts). Use auto-fill when you want a fixed item size and are happy leaving trailing empty space. If you only ever have enough items to fill a row, the two behave the same.
Once your layout is fluid, you can still add media queries to fine-tune spacing or swap the whole structure at specific breakpoints.
Step 5: Place Items Precisely With Line Numbers
Grid numbers its lines starting at 1 on the left/top. You position an item by telling it which lines to start and end on.
.featured {
grid-column: 1 / 3; /* span from column line 1 to line 3 = 2 columns */
grid-row: 1 / 2;
}
The span keyword is often clearer than counting lines:
.featured {
grid-column: span 2; /* take up 2 columns, wherever it lands */
grid-row: span 2;
}
This is how you build a "featured" tile that is twice as wide as its neighbors, or a hero cell that dominates a masonry-style grid — no absolute positioning, no float hacks.
Step 6: Name Regions With grid-template-areas
For full-page layouts, line numbers get abstract. Named template areas let you draw the layout in your CSS as ASCII art, which is far easier to read and maintain.
.page {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
gap: 1rem;
min-height: 100vh;
}
.page > header { grid-area: header; }
.page > nav { grid-area: sidebar; }
.page > main { grid-area: main; }
.page > footer { grid-area: footer; }
Each quoted string is a row; each word is a column cell. Repeating a name (like header) spans that region across cells. This is the classic "holy grail" layout — header, footer, sidebar, and content — in a form you can read at a glance and rearrange for mobile in a media query.
To collapse it to a single column on small screens, redraw the map:
@media (max-width: 600px) {
.page {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
}
}
Aligning Items Inside the Grid
Grid has two axes, so it has alignment properties for each. These control how items sit inside their cells and how tracks sit inside the container.
| Property | Applies to | Axis it controls |
|---|---|---|
justify-items |
items within their cell | inline (row / horizontal) |
align-items |
items within their cell | block (column / vertical) |
justify-content |
the whole grid within the container | inline |
align-content |
the whole grid within the container | block |
place-items |
shorthand for align-items + justify-items |
both |
Centering a single item inside its cell is a one-liner:
.cell {
display: grid;
place-items: center;
}
place-items: center is one of the cleanest ways to center content both horizontally and vertically. It is worth comparing against the other centering techniques and the alignment model in Flexbox, which shares the same justify/align vocabulary.
Implicit vs Explicit Grids
The tracks you define with grid-template-columns and grid-template-rows form the explicit grid. When you add more items than fit, Grid creates implicit tracks to hold them. Control their size with grid-auto-rows:
.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 200px; /* every new row is 200px tall */
gap: 1rem;
}
Without grid-auto-rows, implicit rows size to their content, which is often what you want for text but not for uniform image tiles.
A Complete Responsive Example
Here is everything combined — a page shell that is fluid by default and collapses cleanly on mobile:
.site {
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-areas:
"header"
"hero"
"cards"
"footer";
gap: 2rem;
max-width: 1200px;
margin-inline: auto;
padding: 1rem;
}
.site > header { grid-area: header; }
.site > .hero { grid-area: hero; }
.site > footer { grid-area: footer; }
.cards {
grid-area: cards;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1rem;
}
The outer grid stacks the page sections; the inner .cards grid handles the responsive card row. Two grids, no media queries, and it works from a 320px phone to a wide desktop.
minmax(0, 1fr)instead of plain1frprevents a common overflow bug where long content or images refuse to shrink below their intrinsic size. Reach for it whenever a grid item overflows its track.
CSS Grid vs Flexbox: Which to Reach For
They are not rivals — most real interfaces use both. The short version:
| Use Grid when… | Use Flexbox when… |
|---|---|
| You need rows and columns aligned together | You need items in a single row or column |
| You're building the overall page structure | You're spacing items inside one component |
| Cells should line up across both axes | Content length should drive the sizing |
| You want named layout regions | You want simple wrapping or distribution |
A reliable rule of thumb: Grid for the layout, Flexbox for the content inside each region. For a deeper decision guide with side-by-side code, see Flexbox vs CSS Grid.
Common Grid Mistakes to Avoid
- Using margins for gutters. Use
gap. It never doubles up or leaks onto outer edges. - Forgetting
minmax(0, 1fr). Plain1frwon't shrink below content size, causing overflow with long words or images. - Setting widths on grid items. Let the tracks (
grid-template-columns) define sizing; item widths fight the grid. - Reaching for media queries too early.
auto-fit+minmax()handles most responsive cases on its own. - Confusing
auto-fitandauto-fill. They only differ when items are sparse —auto-fitcollapses empty tracks,auto-fillkeeps them.
Where to Go Next
You now have every core Grid concept: containers, fr units, repeat(), gap, responsive auto-fit grids, line-based placement, and named areas. The fastest way to internalize them is to rebuild a layout you already know — a dashboard, a blog index, a pricing page — using nothing but Grid.
From here, pair Grid with the alignment and sizing patterns in the full Flexbox guide, and layer in breakpoints with CSS media queries when you need structural changes rather than fluid reflow. Master those three tools together and there is very little on the web you cannot lay out.