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
- Step 7: Align Nested Cards With subgrid
- Aligning Items Inside the Grid
- Implicit vs Explicit Grids
- A Complete Responsive Example
- Why 1fr Can Still Overflow
- CSS Grid vs Flexbox: Which to Reach For
- Keep Visual Order and Reading Order Aligned
- Common Grid Mistakes to Avoid
- Where to Go Next
- References and testing note
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.
MDN's CSS Grid guide marks core Grid as widely available across browsers since October 2017. That covers current Chrome, Firefox, Safari, and Edge releases, but production support still depends on the exact feature you use. Check the feature, not only the words "CSS Grid": subgrid, for example, arrived later than the original grid container and track features.
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.
Suppose the grid's content box is exactly 1200px wide and 1rem resolves to 16px. Four 250px columns plus three gaps need 1048px, so four columns fit. Five columns plus four gaps need 1314px, so five do not. The remaining 152px is divided by the four 1fr maxima, producing four 288px columns.
That arithmetic is why "four or five columns on a 1200px screen" is not a reliable promise. Viewport width is not necessarily grid width, rem can change, padding and borders consume space, and scrollbar space may matter. Inspect the grid container's content box in developer tools when a breakpoint surprises you.
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";
}
}
Step 7: Align Nested Cards With subgrid
A nested display: grid normally creates an independent set of tracks. Its columns and rows do not automatically line up with the parent. The subgrid value lets a nested grid use the parent grid's track definition instead. MDN's subgrid guide demonstrates that relationship, and CSS Grid Level 2 adds subgrid specifically so nested grids can participate in parent sizing.
Here is a card row whose headings, body copy, and actions align across cards:
.cards {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1rem;
}
.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
}
Each .card spans three parent rows, then adopts those rows through subgrid. Use this when nested content needs shared alignment. Use an ordinary nested grid when the component should size itself independently. Because subgrid support arrived later than core Grid, verify the browsers in your project's support policy before shipping it.
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.
Why 1fr Can Still Overflow
An fr track is flexible, but a grid item can contribute an automatic minimum size based on its content. A long unbroken URL, a preformatted code block, or an image with an intrinsic width can therefore prevent a plain 1fr column from shrinking as far as you expected. The Grid specification documents this automatic minimum size.
Fix the track when the track should be allowed to shrink:
.layout {
grid-template-columns: minmax(0, 1fr) 18rem;
}
Fix the item when that item is the constraint:
.main {
min-width: 0;
}
.main img {
max-width: 100%;
}
These remedies solve different problems. minmax(0, 1fr) changes the track minimum; min-width: 0 permits a particular item to shrink. Neither wraps an unbreakable token by itself, so text may also need overflow-wrap: anywhere and code may need intentional overflow.
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.
Keep Visual Order and Reading Order Aligned
Grid placement can move an item visually without moving it in the document. Keyboard focus, screen-reader reading order, and copy order still follow the source. Do not use grid-row, grid-column, or the order property to turn illogical HTML into a visually tidy interface. Write the HTML in a meaningful order first, then use Grid for layout. If a mobile design needs a fundamentally different sequence, test keyboard navigation and assistive-technology reading order rather than judging only the screenshot.
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.
References and testing note
The feature explanations above were rechecked against MDN's Grid basics, MDN's repeat() reference, MDN's subgrid guide, and the CSS Working Group's Grid Level 2 draft.
Code examples are provided as-is for education. Test them with your actual content, browser-support policy, writing modes, zoom settings, keyboard navigation, and assistive-technology workflow before production.