What is CSS Grid?

CSS Grid is a layout system designed for two-dimensional layouts: rows and columns at the same time. If Flexbox is great for “a row of things” (or “a column of things”), Grid is great for “a whole layout”.

You’ll typically reach for Grid when:

  • You want a predictable layout with both rows and columns.
  • You want to place items in specific spots (and not rely on the DOM order alone).
  • You want responsive columns that wrap nicely without dozens of media queries.

Let’s build the skill step by step, with interactive playgrounds you can click through.

Your first grid: display grid and columns

A grid starts when you set display: grid on a container. Then you define columns (and optionally rows) with grid-template-columns and grid-template-rows.

In this first demo, we’ll create 3 columns and let items automatically fill them row by row.

.grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}
  
.grid {
  display: grid;
  grid-template-columns: 200px 1fr 120px;
}
  
.grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.grid {
  padding: 16px;
  border: 3px solid #111;
  background: #f6f6f6;
}

.item {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  padding: 14px;
  border: 2px solid #111;
  background: #fff;
}
  
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7

A quick mental model:

  • The container defines the grid tracks (columns and rows).
  • The children become grid items and get auto-placed into available cells.
  • When you run out of columns, Grid moves to the next row.

Fractions and repeat

The fr unit means “a fraction of the available space”. It’s one of the reasons Grid feels so natural for layouts.

And when you’re repeating a pattern, use repeat() so your CSS stays readable.

.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}
  
.grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
}
  
.grid {
  display: grid;
  grid-template-columns: 2fr repeat(2, 1fr);
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.grid {
  padding: 16px;
  border: 3px solid #111;
  background: #f6f6f6;
}

.item {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  padding: 14px;
  border: 2px solid #111;
  background: #fff;
}
  
A
B
C
D
E
F
G
H

Tip: repeat(12, 1fr) is a simple way to build a “12-column grid” system. You don’t need it, but it can be handy for page layouts.

Rows: explicit rows and auto rows

Columns get the spotlight, but rows matter too. You can define explicit rows with grid-template-rows.

For rows created automatically (implicit rows), you control their size with grid-auto-rows.

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 80px 80px;
}
  
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 70px;
}
  
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: minmax(60px, auto);
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.grid {
  padding: 16px;
  border: 3px solid #111;
  background: #f6f6f6;
}

.item {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  padding: 14px;
  border: 2px solid #111;
  background: #fff;
}

.item.big {
  padding: 24px 14px;
}
  
1
2
3 (taller content)
4
5
6
7

minmax() is a superstar:

  • The first value is the minimum size.
  • The second value is the maximum size.
  • Using auto as the max lets content grow when needed.

Gap: spacing without margin gymnastics

Use gap to add space between grid cells. This avoids the “margin on children” headache and keeps spacing consistent.

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0px;
}
  
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
  
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  row-gap: 18px;
  column-gap: 6px;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.grid {
  padding: 16px;
  border: 3px solid #111;
  background: #f6f6f6;
}

.item {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  padding: 14px;
  border: 2px solid #111;
  background: #fff;
}
  
Alpha
Beta
Gamma
Delta
Epsilon
Zeta

If your layout suddenly feels “too cramped” or “too airy”, gap is often the first dial to turn.

Implicit grid and auto-placement

When you don’t explicitly place items, Grid auto-places them. Most of the time that’s exactly what you want.

The property that controls the auto-placement algorithm is grid-auto-flow. The default is row (fill a row, then move down).

.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-flow: row;
}
  
.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-flow: column;
}
  
.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-flow: row dense;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.grid {
  padding: 16px;
  border: 3px solid #111;
  background: #f6f6f6;
  gap: 12px;
}

.item {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  padding: 14px;
  border: 2px solid #111;
  background: #fff;
}

.item.wide {
  grid-column: span 2;
}
  
1
2 (span 2)
3
4
5
6
7 (span 2)
8
9

About dense:

  • It lets Grid “backfill” gaps when items span multiple cells.
  • It can change the visual order compared to the DOM order.
  • That can be fine for purely decorative grids, but be careful with content that needs a logical reading order.

Interactive controls: tweak columns and gap with sliders

Let’s make the grid feel more “alive”. In this playground, sliders control the three column fractions and the gap value.

Notice how changing fractions changes the layout without needing fixed widths. This is one of Grid’s sweet spots.

.grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 12px;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.grid {
  padding: 16px;
  border: 3px solid #111;
  background: #f6f6f6;
}

.item {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  padding: 14px;
  border: 2px solid #111;
  background: #fff;
}

.item strong {
  font-weight: 700;
}
  
Card A
Wants more space
Card B
Happy to be smaller
Card C
Also smaller
Card D
Wraps to the next row
Card E
Grid is doing the math

Responsive grids: minmax and auto-fit auto-fill

One of the most useful Grid patterns is “cards that wrap nicely”. Instead of picking a column count per breakpoint, you can say:

  • Each card should be at least this wide.
  • Then fit as many columns as possible.

That’s exactly what repeat(auto-fill, minmax(...)) does.

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
  
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.grid {
  padding: 16px;
  border: 3px solid #111;
  background: #f6f6f6;
  gap: 12px;
}

.item {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  padding: 14px;
  border: 2px solid #111;
  background: #fff;
}
  
Card 1
Card 2
Card 3
Card 4
Card 5
Card 6
Card 7

auto-fit vs auto-fill (the friendly version):

  • auto-fit collapses “empty tracks” so items stretch and fill the row.
  • auto-fill keeps the tracks reserved, even if there aren’t enough items to fill them.

In many “card grid” cases, auto-fill is the better choice, since it keeps the layout more consistent as items are added or removed.

Alignment in Grid

Grid alignment comes in two flavors:

  • Item alignment inside each cell (how content aligns within its grid area)
  • Track alignment inside the container (how the whole grid aligns if there’s extra space)

The most common beginner-friendly trio is: justify-items, align-items, and place-items.

justify-items with a radio group

justify-items controls horizontal alignment of items within their grid cells. Use the radio buttons to switch values and see what changes.

justify-items:
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  justify-items: stretch;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.grid {
  padding: 16px;
  border: 3px solid #111;
  background: #f6f6f6;
  gap: 12px;
}

.item {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  padding: 14px;
  border: 2px solid #111;
  background: #fff;
  width: 80px;
}
  
One
Two
Three
Four
Five
Six

Notes:

  • stretch is the default for many sizing situations, so items expand to fill the cell.
  • If your items have an explicit width (like in this demo), the difference between values becomes very obvious.

place-items and content alignment

place-items is shorthand for align-items and justify-items. It’s great when you want consistent alignment in both directions.

Meanwhile, justify-content and align-content align the whole grid tracks inside the container when there’s extra space (usually when the grid is smaller than its container).

.grid {
  display: grid;
  grid-template-columns: repeat(3, 120px);
  place-items: start;
  justify-content: start;
}
  
.grid {
  display: grid;
  grid-template-columns: repeat(3, 120px);
  place-items: center;
  justify-content: center;
}
  
.grid {
  display: grid;
  grid-template-columns: repeat(3, 120px);
  place-items: end;
  justify-content: space-between;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.grid {
  width: 100%;
  min-height: 240px;
  padding: 16px;
  border: 3px solid #111;
  background: #f6f6f6;
  gap: 12px;
}

.item {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  padding: 14px;
  border: 2px solid #111;
  background: #fff;
}
  
Box 1
Box 2
Box 3
Box 4
Box 5
Box 6

Placing items: grid-column and grid-row

Auto-placement is great, but sometimes you want an item to span columns or land in a specific spot. That’s where grid-column and grid-row come in.

The most beginner-friendly pattern is span: grid-column: span 2 means “take up two columns”.

.feature {
  grid-column: span 2;
}
  
.feature {
  grid-column: 1 / -1;
}
  
.feature {
  grid-column: 2 / 4;
  grid-row: 1 / 3;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 80px;
  padding: 16px;
  border: 3px solid #111;
  background: #f6f6f6;
  gap: 12px;
}

.item {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  padding: 14px;
  border: 2px solid #111;
  background: #fff;
  display: grid;
  place-items: center;
}

.feature {
  background: #fff;
  border-style: dashed;
}
  
Feature
2
3
4
5
6

A couple of helpful details:

  • Line numbers start at 1.
  • Negative line numbers count from the end, so -1 is the last line.
  • grid-column: 1 / -1 is a popular trick for “span the full width”.

Named layouts with grid-template-areas

grid-template-areas lets you name regions of the grid and place items into them. It’s often described as “ASCII art layout”, and honestly, that’s not wrong.

This can be quite readable for page layouts like header / sidebar / main / footer.

.grid {
  display: grid;
  grid-template-columns: 220px 1fr;
  grid-template-areas:
    "nav main";
}

.nav {
grid-area: nav;
}

.main {
grid-area: main;
} 
.grid {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-areas:
    "nav"
    "main";
}

.nav {
  grid-area: nav;
}

.main {
  grid-area: main;
}
  
.grid {
  display: grid;
  grid-template-columns: 100px 1fr 100px;
  grid-template-areas:
    "nav main aside";
}

.nav {
  grid-area: nav;
}

.main {
  grid-area: main;
}

.aside {
  grid-area: aside;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.grid {
  padding: 16px;
  border: 3px solid #111;
  background: #f6f6f6;
  gap: 12px;
  min-height: 240px;
}

.panel {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  padding: 14px;
  border: 2px solid #111;
  background: #fff;
}

.panel p {
  margin: 0;
}
  

Main

Your content area

Aside

Extras and widgets

A few rules for areas:

  • Each row in grid-template-areas must have the same number of columns (same number of “cells”).
  • Use a dot . for an empty cell.
  • Names must form rectangles (no weird L-shaped areas).

A practical layout pattern: header, content, and footer

Here’s a simple layout you can reuse constantly:

  • A header that spans full width
  • A content area with a sidebar on large screens
  • A footer that spans full width

We’ll use areas for clarity, plus a media query to switch to a single column on smaller screens.

.page {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
}
  
@media (max-width: 700px) {
  .page {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "main"
      "sidebar"
      "footer";
  }
}
  
.page {
  display: grid;
  grid-template-columns: minmax(180px, 260px) 1fr;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.page {
  padding: 16px;
  border: 3px solid #111;
  background: #f6f6f6;
  gap: 12px;
  min-height: 320px;
}

.panel {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  padding: 14px;
  border: 2px solid #111;
  background: #fff;
}

.header {
  grid-area: header;
}

.sidebar {
  grid-area: sidebar;
}

.main {
  grid-area: main;
}

.footer {
  grid-area: footer;
}

.panel p {
  margin: 0;
}
  

Header

Logo, nav, search

Main

Articles, products, content

If you only memorize one Grid layout approach for real projects, this one is a solid choice. It’s readable, flexible, and scales well.

Common Grid pitfalls (and how to fix them)

  • “My grid items aren’t aligning.”

    Check whether you meant justify-items / align-items (items inside cells), or justify-content / align-content (grid tracks inside the container).

  • “Why are my columns overflowing?”

    Try minmax(0, 1fr) for columns that contain long content, especially if you see overflow from long words or code.

  • “I used dense and my order looks weird.”

    That’s expected. dense can backfill gaps and change visual order. Avoid it for content where reading order matters.

  • “I’m mixing margins, gaps, and padding and everything feels inconsistent.”

    Use gap for spacing between grid items, and padding on the container for spacing around the grid.

Quick Grid cheat sheet

  • Turn it on: display: grid
  • Columns: grid-template-columns
  • Rows: grid-template-rows and grid-auto-rows
  • Spacing: gap, row-gap, column-gap
  • Responsive cards: repeat(auto-fill, minmax(180px, 1fr))
  • Align items in cells: justify-items, align-items, place-items
  • Align the grid in container: justify-content, align-content
  • Place items: grid-column, grid-row, span
  • Named layouts: grid-template-areas and grid-area

CSS Grid Conclusion

CSS Grid is a powerful tool for building complex, responsive layouts with less code and more flexibility than ever before. By mastering the core concepts and properties, you can create designs that adapt gracefully to different screen sizes and content needs.

Learn more about its closely related layout option, inline-grid, in the CSS Display Inline-Grid Interactive Tutorial.

If you don't already know about flex, check out the CSS Flexbox Interactive Tutorial.