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 lays out rows and columns together. Start with `display: grid`, define tracks with `grid-template-columns`, and use `gap` for gutters. For responsive cards, `repeat(auto-fit, minmax(250px, 1fr))` creates as many 250px-minimum tracks as the container can hold. Then add line placement or named areas only where the design needs them, and test overflow, source order, and feature support with real content.

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:

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 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.

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

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.

FAQ

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

Use `grid-template-columns: repeat(auto-fit, minmax(250px, 1fr))` with `gap`. The minimum sets the narrowest track, `auto-fit` creates as many tracks as the container can hold and collapses empty ones, and `1fr` shares remaining space. Media queries are still useful when the layout itself, rather than only track count, must change.

How many 250px Grid columns fit in 1200px?

With a 1200px content box and a 16px gap, four columns fit: 4 x 250px plus 3 x 16px equals 1048px. Five need 1314px. With `minmax(250px, 1fr)`, the remaining 152px is shared, producing four 288px columns. Padding, borders, scrollbar space, and a different root font size change the result.

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

Both repeat tracks to fill available space. After items are placed, `auto-fit` collapses empty repeated tracks, allowing occupied tracks to stretch. `auto-fill` retains those empty tracks. When enough items occupy every track, the results can look identical. Use developer tools with a short item list to see the difference.

Why does 1fr overflow a CSS Grid?

A grid item can contribute an automatic minimum size based on its content, so a long URL, code block, or intrinsically wide image can keep a `1fr` track from shrinking. Use `minmax(0, 1fr)` when the track may shrink, `min-width: 0` on the constrained item, and an appropriate text or media overflow rule.

What does subgrid do?

A normal nested grid creates independent tracks. `subgrid` lets the nested grid use the relevant parent track definition, which is useful when headings, descriptions, and actions must align across cards. The child must span parent tracks before adopting them. Check the exact feature against your project's browser-support policy because subgrid arrived after core Grid.

Should I use CSS Grid or Flexbox?

Use Grid when relationships across rows and columns drive the layout. Use Flexbox when distribution along one main axis drives a component. Grid for page layout and Flexbox inside components is a useful starting heuristic, not a rule; many components use Grid, and many pages use Flexbox.