TH The Sass Way
Layout: Flexbox & Grid

CSS Grid Tutorial: Build a Responsive Layout Step by Step

CSS Grid Tutorial: Build a Responsive Layout Step by Step
tldrCSS Grid is a two-dimensional layout system for building rows and columns at once. Start with `display: grid`, define tracks with `grid-template-columns`, and use the `fr` unit for flexible widths. For a responsive layout without media queries, use `grid-template-columns: repeat(auto-fit, minmax(250px, 1fr))` and add spacing with `gap`.

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:

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 plain 1fr prevents 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

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.

FAQ

How do I make a CSS Grid responsive without media queries?

Use `grid-template-columns: repeat(auto-fit, minmax(250px, 1fr))` with a `gap`. The `minmax()` sets a minimum column width and lets columns grow to an equal share of space, while `auto-fit` fits as many columns as possible and wraps the rest to new rows. The grid reflows from many columns on desktop down to one on mobile automatically.

What is the fr unit in CSS Grid?

The `fr` unit represents a fraction of the available space in the grid container. `grid-template-columns: 1fr 1fr 1fr` creates three equal columns, while `2fr 1fr` makes the first column twice as wide as the second. Unlike percentages, `fr` accounts for `gap` and fixed-width tracks automatically, so columns always add up correctly.

What is the difference between auto-fit and auto-fill?

Both repeat columns to fill a row, but they differ when items are sparse. `auto-fit` collapses empty tracks so existing items stretch to fill the row. `auto-fill` keeps empty tracks at their minimum width, leaving trailing gaps. When you have enough items to fill every row, the two behave identically. Use `auto-fit` for most card layouts.

Should I use CSS Grid or Flexbox?

Use CSS Grid for two-dimensional layouts where rows and columns align together, such as overall page structure. Use Flexbox for one-dimensional layouts, like spacing items in a single row or column inside a component. A common rule of thumb is Grid for the page layout and Flexbox for the content inside each region. Most real interfaces use both together.

How do I center an item with CSS Grid?

Set `display: grid` on the container and add `place-items: center`, which is shorthand for `align-items: center` and `justify-items: center`. This centers the content both horizontally and vertically inside the grid cell. It is one of the shortest ways to center an element in modern CSS and works in all current browsers.

What does grid-template-areas do?

`grid-template-areas` lets you name regions of your layout and arrange them visually as strings in your CSS, like ASCII art. Each quoted string is a row and each word is a cell; repeating a name spans that region. You then assign items with `grid-area: name`. It makes full-page layouts like the holy grail pattern far easier to read and rearrange for mobile.