<< Home

Chapter 14: CSS Grid for Fully Responsive Layout

Grid is a two-dimensional layout system that lets you place items in rows and columns with precision and ease. You define a grid on a container, then position children into cells or named areas. The same rules work for small screens and large screens; you can change track sizes, placement and gaps at different breakpoints without rewriting your HTML.

Grid areas and auto placement

A grid begins when a container uses display: grid. You then define columns and rows, and Grid will place items automatically unless you give them explicit coordinates. Named areas make layouts easier to read and maintain; the names describe intent and the browser handles the math.

Creating tracks

Tracks are the columns and rows of your grid. You can size them with fixed units, flexible fr units, or functions like minmax(). Start simple, then refine as your content and breakpoints demand.

.grid {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  grid-template-rows: auto auto;
  gap: 1rem;
}

This creates three columns (flexible in a 1:2:1 ratio) and two rows that size to their content. The gap property sets consistent spacing between grid cells.

Describing intent

Named areas make a layout read like a wireframe. Each quoted string represents a row; repeating a name spans that area across adjacent tracks.

.grid {
  display: grid;
  grid-template-columns: 200px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "sitehead sitehead"
    "sidenav  main"
    "sitefoot sitefoot";
}
.sitehead { grid-area: sitehead; }
.sidenav  { grid-area: sidenav; }
.main     { grid-area: main; }
.sitefoot { grid-area: sitefoot; }

The CSS reads like a plan: header at the top across both columns, sidebar and main content in the middle, and footer across the bottom.

💡 Use short, meaningful area names like sitehead, main, and sitefoot; this keeps your template readable and your selectors concise.

Letting Grid place items

If you omit explicit positions and areas, Grid fills available cells in source order. You can influence the fill direction with grid-auto-flow and control sizes of implicit tracks that appear beyond your template.

.gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 12rem;
  grid-auto-flow: row; /* or column; or dense for backfilling */
  gap: 0.75rem;
}

Here items flow by rows, each implicit row is 12rem tall, and the template defines three equal columns. Using dense can reduce gaps, but it may rearrange source order, which can affect reading order for assistive tech and keyboard navigation.

Responsive units and media queries

Grid shines when paired with flexible units; you can blend fr, percentages, and functions that adapt to available space. Media queries let you switch templates at logical breakpoints based on content needs.

Flexible tracks

minmax() gives a track a protective minimum and an elastic maximum. Combining it with fr and auto-fit or auto-fill yields rows of cards that wrap neatly on any screen.

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 1rem;
}

Each card gets at least 14rem of width and stretches up to a flexible fraction. The number of columns changes automatically as space allows.

When to use auto-fit versus auto-fill

Both keywords create as many columns as will fit. The difference appears when leftover space remains. auto-fit collapses empty tracks so existing items grow to fill the row; auto-fill preserves the tracks, which can be useful if you expect more items later.

.fit  { grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
.fill { grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); }

Choose the one that matches your layout goals. If you want cards to stretch and avoid gaps, pick auto-fit. If you want a regular rhythm that anticipates more content, pick auto-fill.

Switching templates with media queries

Sometimes the relationship between regions changes at wider widths. You can keep a single source order and swap templates at a breakpoint that suits your content rather than a specific device.

.page {
  display: grid;
  grid-template-areas:
    "sitehead"
    "main"
    "sidenav"
    "sitefoot";
  grid-template-columns: 1fr;
  gap: 1rem;
}
@media (min-width: 48rem) {
  .page {
    grid-template-areas:
      "sitehead sitehead"
      "sidenav  main"
      "sitefoot sitefoot";
    grid-template-columns: 16rem 1fr;
  }
}

On small screens, regions stack in a single column. At 48rem and above, the sidebar moves beside the main content while header and footer span both columns.

⚠️ Keep the DOM order logical for reading and focus navigation; use Grid to reposition only the visual layout. Screen readers and keyboard users benefit from a source that matches the story you intend.

A full page layout with grid

This example assembles a complete page using named areas, flexible tracks, and a single DOM structure. It remains readable on small screens and gains a two-column composition when space allows.

<header class="sitehead">
  <h1>My Site</h1>
</header>
<nav class="sidenav" aria-label="Primary">
  <ul>
    <li><a href="#">Home</a></li>
    <li><a href="#">Articles</a></li>
    <li><a href="#">About</a></li>
  </ul>
</nav>
<main class="main">
  <article>
    <h2>Latest</h2>
    <p>Welcome to the site. Here is an update.</p>
  </article>
</main>
<footer class="sitefoot">
  <p>© <span id="year">2025</span></p>
</footer>
.page {
  display: grid;
  grid-template-areas:
    "sitehead"
    "main"
    "sidenav"
    "sitefoot";
  grid-template-columns: 1fr;
  gap: 1.25rem;
  padding: 1rem;
}
.sitehead { grid-area: sitehead; }
.sidenav  { grid-area: sidenav; }
.main     { grid-area: main; }
.sitefoot { grid-area: sitefoot; }

@media (min-width: 56rem) {
  .page {
    grid-template-areas:
      "sitehead sitehead"
      "sidenav  main"
      "sitefoot sitefoot";
    grid-template-columns: 18rem 1fr;
    align-items: start;
  }
}

.sitehead, .sidenav, .main, .sitefoot {
  background: #f7f7f9;
  border: 1px solid #e0e0e6;
  padding: 1rem;
  border-radius: 0.5rem;
}

Attach the page class to a wrapper element that contains the four regions. The content stacks naturally on small viewports; at wider widths the layout breathes and aligns to a familiar pattern.

Upgrading the photo gallery into a responsive mosaic

A gallery benefits from automatic wrapping and variable row heights. You can keep a simple markup list and let Grid create a masonry-style mosaic with minimal CSS by mixing fixed row heights and item spans.

Base grid for the gallery

Define equal columns with a flexible minimum and a sensible gap. Use a short implicit row height; items can then span rows to create natural variation.

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  grid-auto-rows: 8rem;
  gap: 0.75rem;
}

This layout adapts to screen width and maintains even spacing between images.

Spanning rows for natural variation

Apply a class to items that should be taller. The span value multiplies the implicit row height; this produces a pleasing mosaic that still aligns to an underlying rhythm.

.gallery figure { margin: 0; }
.gallery img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gallery .tall  { grid-row: span 2; }
.gallery .grand { grid-row: span 3; }
<section class="gallery" aria-label="Photo gallery">
  <figure class="tall"><img src="img1.jpg" alt="Sunlit forest"></figure>
  <figure><img src="img2.jpg" alt="City skyline"></figure>
  <figure class="grand"><img src="img3.jpg" alt="Mountain lake"></figure>
  <figure><img src="img4.jpg" alt="Desert road"></figure>
  <figure class="tall"><img src="img5.jpg" alt="Waves at dusk"></figure>
</section>

Use emphasis to mark special items when it makes the story clearer, then apply a span that matches their visual weight. Keep alt text short and meaningful so the gallery remains accessible.

Enhancing flow with subgrid when supported

Where supported, subgrid lets child grids align with parent tracks, which keeps gutters and vertical rhythm consistent across nested components. You can progressively enhance by checking support and falling back to a standard grid.

@supports (grid-template-rows: subgrid) {
  .card-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1rem; }
  .card { display: grid; grid-template-rows: subgrid; grid-row: span 3; }
}

Browsers that support subgrid will align nested tracks automatically; others will use the regular grid rules without breaking layout.

When you need general placeholders in examples, show omitted declarations clearly: selector { … }. This signals that other properties may be present without implying a specific set.

⚠️ Choosing between Flexbox and Grid often comes down to the shape of the layout you want. Flexbox works best when you arrange items along a single axis and care about alignment, spacing or the occasional wrap. Grid is stronger when you think in two dimensions and want to place items across rows and columns with named areas or explicit spans. A simple way to decide is to picture the layout as a line or a matrix. If it feels like a line, Flexbox fits. If it feels like a matrix, Grid fits.