Web Development

CSS Grid Layout: A Complete Guide

CSS Grid Layout: A Complete Guide

CSS Grid is the two-dimensional layout system that CSS needed for two decades. While Flexbox handles one-dimensional layouts — a row or a column — Grid works in both dimensions simultaneously, giving you precise control over rows and columns without the hacks, float clears, and framework dependencies that defined CSS layout for years. Every modern browser supports CSS Grid, and it handles everything from simple card grids to complex application layouts.

This guide covers CSS Grid from basic grids through advanced techniques like subgrid, auto-placement algorithms, and responsive patterns that require zero media queries.

Creating Your First Grid

Any element becomes a grid container when you set display: grid. Its direct children become grid items automatically:

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
}
<div class="grid">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
  <div>Item 4</div>
  <div>Item 5</div>
  <div>Item 6</div>
</div>

This creates a three-column grid with equal-width columns. The 1fr unit represents one fraction of the available space, and gap adds consistent spacing between all grid items without affecting the outer edges. No margin hacks, no negative margins on containers — just clean, predictable spacing.

Defining Tracks: Columns and Rows

Grid tracks are the columns and rows that make up the grid. You define them with grid-template-columns and grid-template-rows, and CSS Grid accepts a wide range of sizing values:

/* Fixed pixel widths */
grid-template-columns: 250px 500px 250px;

/* Fractional units — distribute remaining space proportionally */
grid-template-columns: 1fr 2fr 1fr;

/* Mix fixed and flexible */
grid-template-columns: 250px 1fr 1fr;

/* Repeat notation for patterns */
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(3, 1fr 2fr); /* alternating pattern */

/* Auto-sizing — content determines the width */
grid-template-columns: auto 1fr auto;

/* Minimum and maximum with minmax() */
grid-template-columns: minmax(200px, 1fr) 2fr minmax(200px, 1fr);

/* Named lines for semantic placement */
grid-template-columns: [sidebar-start] 250px [sidebar-end content-start] 1fr [content-end];

Implicit vs Explicit Tracks

Explicit tracks are the ones you define. Implicit tracks are created automatically when items overflow beyond your defined grid. You can control implicit track sizing:

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 80px 200px; /* two explicit rows */
  grid-auto-rows: 150px; /* implicit rows default to 150px */
  grid-auto-flow: row; /* items flow by row (default) */
}

Placing Items on the Grid

Grid items can be placed explicitly using line numbers, span notation, or named areas. Line numbers start at 1 (not 0), and negative numbers count from the end:

/* Place by line numbers */
.header {
  grid-column: 1 / 4;     /* column line 1 to line 4 (spans 3 columns) */
  grid-row: 1 / 2;        /* first row */
}

/* Span notation */
.featured {
  grid-column: span 2;    /* span 2 columns from current position */
  grid-row: span 2;       /* span 2 rows from current position */
}

/* Shorthand: grid-column is grid-column-start / grid-column-end */
.sidebar {
  grid-column: 1 / 2;
  grid-row: 2 / 4;        /* rows 2 through 3 */
}

/* Negative line numbers count from the end */
.full-width {
  grid-column: 1 / -1;    /* first line to last line = full width */
}

The grid-area Shorthand

/* grid-area: row-start / column-start / row-end / column-end */
.hero {
  grid-area: 1 / 1 / 3 / -1; /* rows 1-2, all columns */
}

Grid Template Areas

Template areas provide the most readable way to define complex layouts. You draw the layout in CSS using strings that map to area names:

.page {
  display: grid;
  grid-template-areas:
    "header  header  header"
    "sidebar content aside"
    "footer  footer  footer";
  grid-template-columns: 220px 1fr 220px;
  grid-template-rows: auto 1fr auto;
  min-height: 100vh;
  gap: 1rem;
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.aside   { grid-area: aside; }
.footer  { grid-area: footer; }

Each string represents a row, and each word within the string represents a column. Using the same name across multiple cells makes that element span those cells. A dot (.) represents an empty cell:

/* Layout with an empty cell */
grid-template-areas:
  "header header header"
  "sidebar content ."
  "footer footer footer";

Template areas make responsive redesigns straightforward — change the area definitions in a media query without touching the HTML. This aligns with responsive design principles that prioritize CSS-driven layout changes over DOM restructuring.

Responsive Grids Without Media Queries

One of CSS Grid’s most practical patterns uses auto-fill or auto-fit with minmax() to create grids that adapt to any screen width without a single media query:

/* Cards that auto-wrap based on available space */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1.5rem;
}

This creates as many columns as will fit, with each column at least 280px wide and stretching equally to fill remaining space. On a 1200px container, you get four columns. On a 600px container, you get two. On a 320px mobile screen, you get one. All without media queries.

auto-fill vs auto-fit

/* auto-fill: creates empty tracks if space allows */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));

/* auto-fit: collapses empty tracks, stretching items to fill space */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));

The difference matters when you have fewer items than columns. With auto-fill, empty tracks maintain their size, keeping items at their minimum width. With auto-fit, empty tracks collapse to zero, and existing items stretch to fill the container. For most card layouts, auto-fill looks better.

Alignment and Spacing

CSS Grid inherits alignment properties from Flexbox but applies them in two dimensions:

.grid {
  display: grid;
  grid-template-columns: repeat(3, 200px);
  gap: 1rem;

  /* Align the entire grid within its container */
  justify-content: center;    /* horizontal alignment of the grid */
  align-content: start;       /* vertical alignment of the grid */

  /* Align all items within their cells (default for all items) */
  justify-items: stretch;     /* horizontal alignment within cells */
  align-items: center;        /* vertical alignment within cells */
}

/* Override alignment for a specific item */
.special-item {
  justify-self: end;
  align-self: stretch;
}

The place- Shorthands

/* place-items: align-items / justify-items */
place-items: center stretch;

/* place-content: align-content / justify-content */
place-content: center center;

/* place-self: align-self / justify-self */
place-self: end center;

Subgrid

Subgrid allows nested grid items to participate in the parent grid’s track sizing. This solves a common problem: aligning elements across cards when each card has varying content heights.

/* Parent grid */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1.5rem;
}

/* Each card becomes a subgrid that inherits the parent's row tracks */
.card {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3; /* card spans 3 rows: image, content, actions */
}

.card-image   { /* first row */ }
.card-content { /* second row — all cards align here */ }
.card-actions { /* third row — buttons align across all cards */ }

Without subgrid, card headings, descriptions, and buttons would be at different vertical positions depending on content length. Subgrid forces them into alignment by sharing the parent grid’s row definitions. This feature has reached full browser support in 2025 across Chrome, Firefox, Safari, and Edge.

Common Layout Patterns

Holy Grail Layout

.layout {
  display: grid;
  grid-template:
    "header header header" auto
    "nav    main   aside"  1fr
    "footer footer footer" auto
    / 200px 1fr 200px;
  min-height: 100vh;
  gap: 0;
}

@media (max-width: 768px) {
  .layout {
    grid-template:
      "header" auto
      "nav"    auto
      "main"   1fr
      "aside"  auto
      "footer" auto
      / 1fr;
  }
}

Magazine Layout

.magazine {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 200px;
  gap: 1rem;
}

.magazine .featured {
  grid-column: span 2;
  grid-row: span 2;
}

.magazine .highlight {
  grid-column: span 2;
}

Sidebar That Collapses on Mobile

.app {
  display: grid;
  grid-template-columns: minmax(0, 250px) 1fr;
  gap: 2rem;
}

@media (max-width: 768px) {
  .app {
    grid-template-columns: 1fr;
  }
}

Centered Content With Full-Bleed Sections

.full-bleed-layout {
  display: grid;
  grid-template-columns:
    1fr
    min(65ch, 100% - 2rem)
    1fr;
}

.full-bleed-layout > * {
  grid-column: 2;
}

.full-bleed-layout > .full-bleed {
  grid-column: 1 / -1;
}

This pattern is popular for blog and article layouts. Regular content sits in a centered column constrained to a readable line length, while hero images, code blocks, or banners can break out to full width. Combined with modern CSS features like container queries, this approach handles most content layout needs.

Grid vs Flexbox: When to Use Which

Grid and Flexbox are complementary tools. Choosing between them depends on the layout you need:

  • CSS Grid — Use when you need to control both rows and columns. Page layouts, card grids, dashboard widgets, form layouts, magazine-style arrangements
  • Flexbox — Use when you need to distribute items along a single axis. Navigation bars, button groups, centering content, flexible spacing between items

In practice, most interfaces use both. Grid defines the overall page structure, and Flexbox handles component-level layout within grid cells. A navigation bar inside a grid header area, a button group inside a card’s footer row, a toolbar with flexible spacing — these are Flexbox use cases nested inside Grid containers.

/* Grid for page layout */
.page {
  display: grid;
  grid-template-columns: 250px 1fr;
  grid-template-rows: auto 1fr auto;
}

/* Flexbox for the navigation inside the header */
.header nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
}

/* Flexbox for button groups inside grid cells */
.card-actions {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
}

Performance and Accessibility

CSS Grid layouts are rendered by the browser’s layout engine and perform well even with hundreds of items. However, keep these considerations in mind:

  • Visual vs DOM order — Grid can reorder items visually (order property, explicit placement), but screen readers follow the DOM order. Ensure the HTML source order makes logical sense for accessibility
  • Avoid excessive nesting — Deeply nested grids increase layout calculation cost. Subgrid reduces nesting by sharing track definitions with parents
  • Use contain: layout on grid containers that update frequently (dashboards, live data displays) to limit the scope of layout recalculations

For performance optimization, measure layout costs using browser DevTools’ Performance panel. Grid layout calculations are fast, but reflow triggered by dynamic content changes in complex grids can affect frame rates.

Debugging CSS Grid

Browser DevTools provide visual grid debugging. In Chrome and Firefox, clicking the grid badge next to an element in the Elements panel overlays the grid lines, track numbers, and area names directly on the page. A good code editor with CSS IntelliSense also helps by autocompleting grid properties and showing valid values as you type. This visual feedback makes it straightforward to diagnose alignment issues, identify implicit tracks, and verify that items are placed in the correct cells.

/* Temporary debugging — make grid lines visible */
.grid * {
  outline: 1px solid rgba(255, 0, 0, 0.3);
}

/* Show grid gaps with a background color on the container */
.grid {
  background: #eee;
}
.grid > * {
  background: white;
}

Frequently Asked Questions

Can I use CSS Grid in production right now?

Yes. CSS Grid has been supported in all major browsers since 2017. Subgrid, the last major feature to gain universal support, works in Chrome, Firefox, Safari, and Edge as of 2025. Global browser support for basic Grid properties exceeds 97%. There is no practical reason to avoid CSS Grid in any project.

Does CSS Grid replace Bootstrap or Tailwind grid systems?

For layout, yes. CSS Grid handles everything that Bootstrap’s 12-column grid system does, with more flexibility and no external dependency. Tailwind’s grid utilities are actually CSS Grid — they generate grid-template-columns and gap properties under the hood. You can use CSS Grid directly or through a utility framework; either approach produces the same result.

How do I handle CSS Grid in emails?

Email clients have poor CSS Grid support. For email templates, continue using table-based layouts or Flexbox with fallbacks. CSS Grid is for web applications and websites where you can count on modern browser rendering engines.

What is the best way to learn CSS Grid visually?

Start with the browser DevTools grid inspector — it shows lines, areas, and track numbers overlaid on your actual layout. Build real layouts: recreate a page you visit daily using only CSS Grid. Practical experience with component-based frameworks that use Grid for page layouts will build your intuition faster than isolated exercises.