4 min read
On this page

CSS Grid

The Two-Dimensional Layout Tool

CSS Grid handles layout in both directions at once -- rows and columns. Where flexbox excels at distributing items along a single axis, grid excels at placing items on a defined two-dimensional plane.

.grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 16px;
}

That creates a three-column grid where each column takes an equal fraction of the available space. Items are placed automatically, filling each cell left to right, top to bottom.

Defining Columns & Rows

grid-template-columns

/* Three equal columns */
.grid { grid-template-columns: 1fr 1fr 1fr; }

/* Shorthand with repeat */
.grid { grid-template-columns: repeat(3, 1fr); }

/* Mixed units */
.grid { grid-template-columns: 250px 1fr 1fr; }

/* Fixed sidebar, flexible main, fixed aside */
.grid { grid-template-columns: 250px 1fr 300px; }

grid-template-rows

/* Explicit row heights */
.grid {
  grid-template-rows: 80px 1fr 60px;
}

/* Rows auto-size to content by default */
/* You only need grid-template-rows when you want explicit control */

The fr Unit

fr stands for fraction. It distributes remaining space after fixed-size columns are accounted for.

.grid {
  grid-template-columns: 200px 1fr 2fr;
  /* 200px is fixed
     remaining space split: 1 part + 2 parts
     if container is 800px: 200px + 200px + 400px */
}

fr is like flex-grow for grid tracks. It is the unit you will use most often.

Gap

.grid {
  display: grid;
  gap: 16px;             /* Equal row and column gap */
  /* Or separately: */
  row-gap: 24px;
  column-gap: 16px;
}

gap replaces the older grid-gap property. It works the same as in flexbox -- space between items, not around the edges.

Placing Items

By default, grid items fill cells automatically in order. For precise control, you place items on specific grid lines.

Grid Lines

Grid lines are numbered starting at 1:

Column lines:  1    2    3    4
               │    │    │    │
Row lines:  1 ─┼────┼────┼────┼
            2 ─┼────┼────┼────┼
            3 ─┼────┼────┼────┼

Placing an Item

.header {
  grid-column: 1 / 4;    /* Span from line 1 to line 4 (all 3 columns) */
  grid-row: 1 / 2;       /* First row */
}

.sidebar {
  grid-column: 1 / 2;    /* First column */
  grid-row: 2 / 3;       /* Second row */
}

.main {
  grid-column: 2 / 4;    /* Second and third columns */
  grid-row: 2 / 3;       /* Second row */
}

Span Syntax

Instead of specifying start and end lines:

.header {
  grid-column: span 3;   /* Span 3 columns from wherever placed */
}

.wide-card {
  grid-column: span 2;   /* Take up 2 columns */
}

Grid Template Areas

Named areas make complex layouts readable:

.page {
  display: grid;
  grid-template-columns: 250px 1fr;
  grid-template-rows: 80px 1fr 60px;
  grid-template-areas:
    "header  header"
    "sidebar main"
    "footer  footer";
  min-height: 100vh;
}

.page-header  { grid-area: header; }
.page-sidebar { grid-area: sidebar; }
.page-main    { grid-area: main; }
.page-footer  { grid-area: footer; }
<div class="page">
  <header class="page-header">Header</header>
  <aside class="page-sidebar">Sidebar</aside>
  <main class="page-main">Main Content</main>
  <footer class="page-footer">Footer</footer>
</div>

The visual shape of the grid-template-areas value mirrors the actual layout. This is one of the most readable layout techniques in CSS.

Use a dot (.) for empty cells:

grid-template-areas:
  "header header header"
  "sidebar main ."
  "footer footer footer";

Auto-Fit & Auto-Fill with Minmax

This is where grid truly shines for responsive design -- responsive grids without media queries.

minmax()

Sets a minimum and maximum size for a track:

.grid {
  grid-template-columns: repeat(3, minmax(200px, 1fr));
  /* Each column: at least 200px, at most 1fr */
}

auto-fit

Creates as many columns as fit, collapsing empty tracks:

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 16px;
}

This creates a responsive card grid:

  • On a 1200px screen: 4 columns of ~280px each
  • On an 800px screen: 3 columns of ~250px each
  • On a 600px screen: 2 columns of ~280px each
  • On a 300px screen: 1 column at full width

No media queries. The grid adapts automatically.

auto-fill

Similar to auto-fit, but keeps empty tracks:

/* auto-fill: empty tracks maintain their space */
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));

/* auto-fit: empty tracks collapse, items stretch to fill */
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));

The difference matters when you have fewer items than columns. auto-fit stretches items to fill the row. auto-fill preserves the column width and leaves empty space.

Use auto-fit in most cases. It gives you the fluid behavior users expect.

Implicit Grid

When items are placed outside the explicit grid (more items than defined cells), the grid creates implicit tracks automatically. Control their size with grid-auto-rows and grid-auto-columns:

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: minmax(100px, auto);  /* At least 100px, grow with content */
}

Alignment

Grid uses the same alignment properties as flexbox, in two dimensions. justify-content and justify-items work horizontally. align-content and align-items work vertically. Individual items override with justify-self and align-self.

.grid {
  display: grid;
  grid-template-columns: repeat(3, 200px);
  justify-content: center;  /* Center the grid in its container */
  align-items: center;      /* Center items vertically within cells */
}

.special-item {
  justify-self: end;        /* Override for one item */
}

Common Patterns

Responsive Card Grid

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 24px;
}

The simplest and most useful grid pattern. Cards fill available space, wrap naturally, and no media queries are needed.

Subgrid

subgrid lets a nested grid inherit track sizes from its parent grid. This solves alignment issues in nested layouts.

.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}

.card {
  display: grid;
  grid-template-rows: subgrid;  /* Inherit row tracks from parent */
  grid-row: span 3;             /* Card spans 3 rows */
}

With subgrid, card titles, bodies, and footers align across cards even when their content lengths differ. Browser support for subgrid is now available in all modern browsers.

Common Pitfalls

  • Using grid for one-dimensional layouts: A single row of items is flexbox territory. Grid adds complexity without benefit for single-axis layouts.
  • Forgetting minmax with auto-fit/auto-fill: repeat(auto-fit, 250px) creates fixed-width columns that may overflow. Use minmax(250px, 1fr) for fluid behavior.
  • Confusing auto-fit and auto-fill: With fewer items than columns, auto-fill leaves empty space while auto-fit stretches items. Test with small item counts.
  • Not setting grid-auto-rows: Without it, implicit rows size to content, which can create uneven row heights. Use grid-auto-rows: minmax(100px, auto) for consistency.
  • Over-specifying placement: Let auto-placement do the work when items follow a natural order. Only use explicit placement for items that break the pattern.
  • Forgetting that gap does not apply to edges: If you need padding around the grid, add padding to the container. gap is only between tracks.

Key Takeaways

  • CSS Grid is a two-dimensional layout tool: rows and columns simultaneously
  • fr units distribute remaining space proportionally, like flex-grow for grid
  • repeat(auto-fit, minmax(250px, 1fr)) creates responsive grids without media queries
  • Grid template areas make complex layouts readable and maintainable
  • Grid excels at page layouts, dashboards, and card grids; flexbox excels at component-level layout
  • Grid items can overlap by placing them on the same grid lines
  • subgrid solves alignment issues in nested grids
  • Auto-placement handles most cases; use explicit placement only when needed