CSS Grid Item Placement

Defining a grid with grid-template-columns and grid-template-rows only sets up the structure — a set of numbered lines forming rows and columns. Item placement is the separate step of telling each child exactly which lines it should start and end at, so you can build layouts where a hero banner spans three columns, a sidebar spans two rows, and a footer stretches edge to edge. Without explicit placement, the browser just drops items into the grid one after another; with it, you get pixel-perfect, magazine-style control. This lesson covers every placement technique: numbered lines, the span keyword, the grid-area shorthand, named lines and named template areas, and what happens when the browser has to guess.

Overview / How it works

When you turn an element into a grid container with display: grid, the browser generates a set of grid lines — numbered dividers that run between and around the tracks (rows/columns) you defined. A grid with 4 columns has 5 vertical grid lines, numbered 1 through 5. A grid with 3 rows has 4 horizontal grid lines. Every grid item is positioned by referencing these lines, not by referencing pixel coordinates or track indexes.

You can count lines from the start (1, 2, 3…) or from the end using negative numbers, where -1 is always the last line regardless of how many tracks the grid has. This is extremely useful for responsive grids where the number of columns might change but you still want an item to reach the final edge.

Four longhand properties do the actual placement work: grid-column-start, grid-column-end, grid-row-start, and grid-row-end. Each one accepts a line number, a named line, or the keyword span followed by a number of tracks (or a named line) to span across. Two shorthands, grid-column and grid-row, combine start and end with a slash. A third shorthand, grid-area, combines all four values in the order row-start / column-start / row-end / column-end — note that this is row-first, which trips up a lot of developers who assume column-first because grid-column is usually written or thought about first.

If an item’s placement isn’t specified at all, the browser’s auto-placement algorithm takes over: by default it walks the grid row by row (controlled by grid-auto-flow, which can also be set to column or dense), placing each item in the first available cell that fits. Items with explicit placement are laid down first; auto-placed items fill in the gaps around them. If two items are told to occupy overlapping cells, the grid lets them overlap — later-in-source items paint on top of earlier ones by default, though grid and flex items respond directly to z-index (no position property is required) if you need to control the stacking order explicitly.

Syntax

.item {
  grid-column-start: <line>;
  grid-column-end: <line>;
  grid-row-start: <line>;
  grid-row-end: <line>;

  /* shorthands */
  grid-column: <start> / <end>;
  grid-row: <start> / <end>;
  grid-area: <row-start> / <column-start> / <row-end> / <column-end>;
}
Value form Meaning
3 An explicit numbered grid line
-1 The last line in that dimension, counted from the end
span 2 Span 2 tracks from the opposite edge, rather than naming an end line
sidebar-end A custom line name defined in grid-template-columns/rows
auto Let the browser’s auto-placement algorithm decide (the default)

Examples

Example 1: Placing with explicit line numbers

.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(3, 100px);
  gap: 10px;
}

.item-a {
  grid-column-start: 1;
  grid-column-end: 3;
  grid-row-start: 1;
  grid-row-end: 2;
}

Result: The grid has 4 equal-width columns and 3 rows of 100px each, separated by 10px gaps. .item-a occupies the top-left area of the grid, stretching across the first two columns (from line 1 to line 3) while staying confined to the first row (from line 1 to line 2, i.e. one row tall).

Line numbers here refer to the boundaries between tracks, not the tracks themselves. Spanning "column 1 to column 3" covers two column tracks, because line 3 is the line after the second column.

Example 2: Using the span keyword and shorthand

.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(3, 100px);
  gap: 10px;
}

.item-b {
  grid-column: 2 / span 2;
  grid-row: 2 / 4;
}

Result: .item-b starts at column line 2 and spans two column tracks (covering columns 2 and 3), and starts at row line 2 while extending to row line 4, so it fills the two bottom rows in that column range. This element visually sits in the middle of the grid, spanning both a horizontal and vertical range.

The span keyword is often easier to reason about than calculating an end line, especially in grids whose column count might change — you can say "span 2 tracks from here" instead of computing an exact line number.

Example 3: Named lines and grid-template-areas

.page {
  display: grid;
  grid-template-columns: [sidebar-start] 200px [sidebar-end main-start] 1fr [main-end];
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  gap: 16px;
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }

Result: The page renders as a classic layout: a full-width header on top, a 200px-wide sidebar on the left beside a flexible main content area, and a full-width footer at the bottom, all separated by 16px gaps. Each element is placed purely by matching its grid-area name to a name in the ASCII-art template, with no line numbers needed anywhere.

grid-template-areas is the most readable placement technique for whole-page layouts: the CSS literally looks like a diagram of the page. Every row string must have the same number of cell names, and a named area must form a solid rectangle — you can’t skip around.

How it works step by step

When the rendering engine lays out a grid, it resolves placement in stages:

  • Step 1 — resolve explicit placements. Any item with grid-row/grid-column/grid-area values is placed at those exact lines first, in DOM order.
  • Step 2 — expand the implicit grid if needed. If an explicit placement references a line beyond the defined tracks (say, line 6 in a 4-column grid), the engine creates extra implicit tracks, sized according to grid-auto-columns/grid-auto-rows (default: auto).
  • Step 3 — auto-place the rest. Items without explicit placement are walked through in DOM order and dropped into the first free cell, scanning row by row (or column by column with grid-auto-flow: column). With dense added, the engine backfills earlier gaps even if it means placing later items out of DOM order visually.
  • Step 4 — compute track sizes and box positions. Once every item has a final row/column range, the engine sizes the tracks (resolving fr units, auto, minmax(), etc.) and calculates the pixel box for each item, including how it aligns within its area via justify-self/align-self.

Understanding this order explains why mixing explicit and auto-placed items can produce gaps: an auto-placed item will never intrude on a cell an explicit item claims, even one placed later in the source.

Common Mistakes

Mistake 1: Treating line numbers as track counts

.item {
  grid-column: 1 / 3;
}

A developer wanting the item to span 3 columns writes 1 / 3, expecting three tracks. But 1 / 3 spans from line 1 to line 3, which covers only two tracks (the first and second columns). This is valid CSS — it just doesn’t produce the intended width.

.item {
  grid-column: 1 / 4;
}

Using line 4 (or equivalently 1 / span 3) correctly spans three column tracks.

Mistake 2: Assuming grid-area is column-first

.item {
  grid-area: 2 / 1;
}

This is syntactically valid, but the author intended "row 1, column 2" and wrote row/column values in that order assuming grid-area reads column-first. Because the actual order is row-start / column-start, this places the item at row 2, column 1 instead — one row too low and one column too far left.

.item {
  grid-area: 1 / 2;
}

Swapping the values to row-start 1, column-start 2 produces the intended placement.

Mistake 3: A non-rectangular grid-template-areas

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

Each string must contain the same number of cell names, and every named area must form a solid rectangle. Here the second row has three cells while the others have two, and main would need to form an irregular shape. Browsers treat the entire grid-template-areas declaration as invalid and ignore it, silently falling back to auto-placement — which can be confusing since nothing in the console flags the error.

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

Keeping every row the same cell count, with each named area rectangular, fixes it.

Best Practices

  • Prefer span over calculating an explicit end line — it reads as intent ("span 2 tracks") rather than arithmetic.
  • Use -1 to reach the last line when you want an item to reach the grid’s edge regardless of how many tracks exist.
  • For whole-page or component layouts, reach for grid-template-areas plus grid-area — the CSS becomes self-documenting and easy to reorder for responsive breakpoints.
  • Remember grid-area‘s order is row-start / column-start / row-end / column-end — row first, not column first.
  • Use named lines ([sidebar-start], [main-end]) in complex grids instead of memorizing numbered lines, especially once a grid has more than 4-5 tracks.
  • Use your browser’s DevTools grid overlay (available in Firefox and Chromium) to visually confirm line numbers and area boundaries instead of guessing.
  • Reach for grid-auto-flow: dense carefully — it visually reorders items to fill gaps, which can create a mismatch between DOM order (used by screen readers and tab order) and visual order.

Practice Exercises

  • Build a 3-column, 3-row grid and place a single item so it spans all three columns in the middle row, using the span keyword.
  • Recreate a common "holy grail" layout (header, footer, left sidebar, main content, right sidebar) using grid-template-areas and grid-area only — no line numbers.
  • Given a 5-column grid, place an item so it always reaches the last column regardless of column count, without hardcoding the number 5 or 6 anywhere in your placement properties.

Summary

  • Grid items are placed by referencing numbered grid lines, not tracks; an n-track grid has n+1 lines.
  • grid-column-start/end and grid-row-start/end (or their grid-column/grid-row shorthands) position items using line numbers, named lines, or span.
  • grid-area is a shorthand for all four values in row-start / column-start / row-end / column-end order.
  • grid-template-areas plus grid-area gives the most readable way to lay out whole page structures, but every row needs equal cell counts and named areas must be rectangular.
  • Unplaced items are auto-placed row by row (or column by column) in DOM order, filling gaps around explicitly placed items; dense packing can reorder items visually away from DOM order.
  • Negative line numbers (like -1) count from the end of the grid, which is useful for reaching the last line in a track-count-agnostic way.