ESC

Type to search the knowledge base.

CSS Grid Fundamentals

Two-dimensional layout with tracks, gaps, placement, fr units, and when grid beats flex for page scaffolding.

beginner5 min read
  • css
  • css-grid
  • layout
  • grid

CSS Grid is a two-dimensional layout system: you define rows and columns, then place items into cells or areas. Flexbox is one-dimensional. Page shells, card matrices, dashboards, and form layouts that need alignment on both axes are grid problems.

Docs: MDN CSS Grid, web.dev Learn CSS — Grid, CSS Grid Layout Module.

Container vs items

.dashboard {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: auto 1fr auto;
  gap: 1rem;
  min-height: 100vh;
}
  • Grid container — display: grid or inline-grid
  • Grid items — in-flow children
  • Tracks — rows and columns
  • Gaps — gutters between tracks (gap, row-gap, column-gap)
  • Cells / areas — intersections of tracks where items live

Only direct children are grid items. Nested UI needs its own grid or flex context.

Defining tracks

/* Fixed + flexible */
.cols {
  display: grid;
  grid-template-columns: 200px 1fr 2fr;
}

/* Repeat helper */
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}

/* Responsive without breakpoints (often) */
.auto {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1rem;
}
Unit / function Meaning
px / rem Fixed track size
% Share of grid container’s size
fr Fraction of free space after fixed tracks
minmax(min, max) Track clamps between min and max
auto Size to content (with max constraints from the algorithm)
repeat(n, …) Repeat a track list

1fr 1fr is two equal free-space shares — not always equal to “50% 50%” once gaps and min-content sizes enter. When a track’s content has a large minimum size, fr tracks can refuse to shrink the way beginners expect. Same family of bug as flex: use minmax(0, 1fr) when you need true equal flexible tracks that can shrink below content size.

.equal-and-shrinkable {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

Placing items

Default: items auto-place in order, one cell each, row by row (grid-auto-flow: row).

.header {
  grid-column: 1 / -1; /* full width: line 1 to last */
}
.sidebar {
  grid-column: 1;
  grid-row: 2 / 4;
}
.main {
  grid-column: 2;
  grid-row: 2;
}

Line numbers start at 1. Negative lines count from the end (-1 is the last line). Spans:

.feature {
  grid-column: span 2;
  grid-row: span 2;
}

Named areas

.page {
  display: grid;
  grid-template-columns: 12rem 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "nav  nav"
    "side main"
    "foot foot";
  gap: 1rem;
}
.page__nav  { grid-area: nav; }
.page__side { grid-area: side; }
.page__main { grid-area: main; }
.page__foot { grid-area: foot; }

Areas must form rectangles. For multi-column responsive shells, redefine grid-template-areas in a media query — clearer than rewriting line numbers.

Alignment

Grid has two levels: tracks in the container and content inside a cell.

.grid {
  display: grid;
  place-items: start stretch; /* align-items + justify-items */
  place-content: center;      /* when grid is smaller than container */
}

.item {
  justify-self: end;
  align-self: center;
}
Property Controls
justify-items / align-items Default alignment of items in their cells
justify-self / align-self Per-item override
justify-content / align-content Packing of the whole grid when tracks don’t fill the container

stretch is the default for items — full cell. For card titles that should top-align while neighbors stretch, set align-items: start on the grid or align-self on items.

Implicit tracks and auto-flow

If you place something outside explicit tracks, or have more items than cells, the grid creates implicit tracks:

.feed {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: minmax(8rem, auto);
  grid-auto-flow: dense; /* pack later items into holes (use carefully) */
}

dense can improve packing for masonry-like card sizes but reorders visual vs DOM risk for accessibility — prefer DOM order that matches reading order.

Nested composition

Grid and flex compose. Typical pattern: grid for page structure, flex for toolbars and media objects inside cells.

<div class="layout">
  <header class="layout__header">…</header>
  <aside class="layout__side">…</aside>
  <main class="layout__main">
    <div class="toolbar">…</div>
    <div class="cards">…</div>
  </main>
</div>
.layout {
  display: grid;
  grid-template-columns: 16rem 1fr;
  grid-template-areas:
    "header header"
    "side   main";
}
.layout__header { grid-area: header; }
.layout__side   { grid-area: side; }
.layout__main   { grid-area: main; min-width: 0; }

.toolbar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: 1rem;
}

min-width: 0 on the main track content again: without it, wide children expand the grid and break the shell.

Grid vs Flexbox (decision table)

Choose grid when… Choose flex when…
Rows and columns both define the design One axis drives the layout
Alignment of items into a matrix matters Nav, chips, toolbars, equal-height row of controls
You want named areas / full-page scaffolding Content size should drive packing along one line
Overlapping/spanning cells are first-class You mainly need grow/shrink along a line

They are not rivals. Use both. Prefer the simpler model that matches the design intent.

Common mistakes

  1. Using grid for a one-axis toolbar — flex is shorter and clearer.
  2. 1fr without minmax(0, 1fr) — overflow from min-content.
  3. Forgetting gap vs margin — double spacing and collapse confusion; use gap in grid/flex.
  4. auto-fit vs auto-fill — auto-fit collapses empty tracks; auto-fill keeps them. Wrong choice leaves awkward empty columns.
  5. Reordering with placement only — visual order ≠ keyboard/screen reader order. Keep DOM logical.

Interview angle

Explain two-dimensional tracks vs flex’s main axis. Write repeat(auto-fit, minmax(…)) from memory. Place a header full-width with grid-column: 1 / -1 or areas. Call out minmax(0, 1fr) and a11y order.

Live coding: holy-grail layout (header, sidebar, main, footer) with areas, then a responsive card grid inside main.

Further reading

Related guides