Grid Properties

Learn to define grid track sizes with grid-template-columns/rows, use repeat() and minmax() to write flexible track definitions, precisely distinguish auto-fit from auto-fill, and control implicit tracks with grid-auto-flow, grid-auto-rows, and grid-auto-columns.

Declaring display: grid gets you a grid container, but the real design work happens in defining your tracks — how many rows and columns you have and how big each one is. CSS Grid gives you a small but extremely expressive set of tools for this: fixed sizes, the flexible fr unit, repeat() to avoid retyping, minmax() to set flexible bounds, and auto-fit/auto-fill for grids that adapt to however many items you have.

grid-template-columns, grid-template-rows, and gap

grid-template-columns defines the number and size of column tracks; grid-template-rows does the same for rows. You can use any CSS length (px, %, em), the keyword auto (size to content), or the special fr unit — a fraction of the remaining free space in the grid container, similar in spirit to flex-grow. grid-template-columns: 1fr 2fr 1fr creates three columns where the middle one is twice as wide as each outer one.

gap (with longhands row-gap and column-gap) inserts consistent spacing between rows and columns, exactly like it does in Flexbox, without adding space at the outer edges of the grid.

• grid-template-columns / grid-template-rows: define track count and sizes • fr unit: a fraction of the remaining free space, after fixed-size tracks are subtracted • gap / row-gap / column-gap: spacing between tracks, not at the outer edge

repeat() and minmax()

repeat() avoids retyping a repeated pattern: repeat(4, 1fr) is identical to writing 1fr 1fr 1fr 1fr. It can repeat any pattern, not just a single value — repeat(2, 100px 1fr) creates four columns following a 100px, 1fr, 100px, 1fr pattern.

minmax(min, max) sets a flexible range for a track: it will never shrink below min and never grow past max. A very common pattern is minmax(200px, 1fr), meaning 'never smaller than 200px, but grow to fill available space beyond that.' This is essential for responsive card grids where items need a sane minimum width before wrapping makes more sense than shrinking further.

• repeat(count, pattern): shorthand for writing a repeated track pattern • minmax(min, max): a track that never shrinks below min or grows beyond max • Commonly combined: repeat(3, minmax(200px, 1fr))

auto-fit vs auto-fill — the precise difference

This is one of the most commonly confused pairs in all of CSS Grid, so it deserves a precise explanation. Both auto-fit and auto-fill are used inside repeat() in place of a fixed count, typically paired with minmax(), e.g. grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)). Both compute how many tracks of at least the minmax minimum size *could* fit in the container's width.

The difference is what happens to the leftover space when there are fewer actual items than tracks that could fit. auto-fill keeps all those computed tracks in the grid — even the empty ones — as rigid placeholder columns. This means your existing items will NOT stretch to fill the leftover space, because empty invisible tracks are silently occupying it. auto-fit instead collapses any track that ends up with no item in it down to zero width, then lets the 1fr component of the minmax expand your real items to consume that freed-up space.

In short: with the same setup, auto-fill tends to leave empty gaps at the end of a row when you don't have enough items, while auto-fit makes your existing items stretch to fill the full row width. If your container is always full of items (never fewer than can fit one row), the two behave identically — the difference only shows up when there's leftover room.

• auto-fill: keeps empty tracks as rigid placeholders — items don't stretch into that space • auto-fit: collapses empty tracks to 0 width — items DO stretch (via 1fr) to fill that freed space • Both compute the same maximum possible track count based on container width and the minmax minimum • They look identical only when there are enough items to fill every possible track

Implicit tracks: grid-auto-flow, grid-auto-rows, grid-auto-columns

The tracks you define with grid-template-columns/rows are called explicit tracks. If you place more items than fit in your explicit grid (or place an item outside of it), Grid automatically generates extra implicit tracks to hold the overflow. grid-auto-rows and grid-auto-columns control the size of those automatically generated tracks — without them, implicit tracks default to auto (sized to content).

grid-auto-flow controls the direction Grid uses when auto-placing items that don't have an explicit position: row (default — fill each row before moving to the next) or column (fill each column before moving to the next). Adding the dense keyword (e.g. grid-auto-flow: row dense) tells Grid to backfill earlier gaps left by larger items, which can reorder items visually to avoid holes — at the cost of potentially placing items out of their source order.

• Explicit tracks: defined by grid-template-columns/rows • Implicit tracks: auto-generated when items overflow the explicit grid • grid-auto-rows / grid-auto-columns: set the size of implicit tracks • grid-auto-flow: row (default) | column, optionally with dense

Common track-definition patterns

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, minmax(100px, auto));
  gap: 16px;
  grid-auto-flow: row;
  grid-auto-rows: 120px;
}

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

Code Examples

The first column is a fixed 200px (perhaps a sidebar), and the remaining two columns split whatever space is left equally, since both use 1fr.

grid-template-columns: 200px 1fr 1fr;

Imagine a 1000px-wide container with only 2 items. auto-fill computes that 4 columns of 200px+ could fit, keeps all 4 as rigid tracks, and leaves 2 of them empty — so the 2 real items sit at 200-250px each with visible empty space after them. auto-fit computes the same 4 possible columns, but collapses the 2 empty ones to 0 width, letting the 1fr in minmax expand the 2 real items to fill the entire row. Expected result: auto-fill: 2 narrow items + visible empty space. auto-fit: 2 items stretched to fill the full row width.

Only columns are defined explicitly here. As log entries are added and Grid creates new rows to hold them, each new implicit row is forced to exactly 48px because of grid-auto-rows, keeping every row a consistent height.

.layout {
  display: grid;
  grid-template-columns: 200px 1fr 1fr;
  gap: 24px;
}
.gallery-fill {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 16px;
}

.gallery-fit {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
}
.log {
  display: grid;
  grid-template-columns: 1fr;
  grid-auto-rows: 48px; /* every implicitly generated row is 48px tall */
}

Important Rules

fr distributes remaining free space AFTER fixed-size and content-sized tracks are already subtracted repeat() can repeat a multi-value pattern, not just a single track size minmax(min, max) bounds a track's size in both directions auto-fill preserves empty tracks (no stretch); auto-fit collapses empty tracks (real items stretch to fill space) grid-auto-rows/columns only affect implicitly generated tracks, never explicit ones defined by grid-template-rows/columns

Real-world usage

repeat(auto-fit, minmax(...)) is the single most common pattern for responsive card and image galleries — it replaces dozens of lines of media queries with one line that reflows the column count automatically based on available width..gallery { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); } /* developer expects the last row's few items to stretch full width, but a gap remains */.gallery { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

When the goal is for existing items to stretch and fill leftover horizontal space, auto-fit is almost always the right choice; auto-fill is for cases where you deliberately want to preserve empty column slots (for example, aligning with a separate fixed grid)..grid { grid-template-columns: repeat(3, 1fr); } /* forgets that a 4th item will create an unstyled implicit row */.grid { grid-template-columns: repeat(3, 1fr); grid-auto-rows: 150px; /* explicitly size any overflow rows */ }

Without grid-auto-rows, any implicit rows created by overflow items default to 'auto' sizing, which can look visually inconsistent next to explicitly sized rows.

[Predict the Output] A 900px-wide grid container uses grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); and contains exactly 2 grid items. Grid calculates that 3 columns of 250px+ could fit. What happens visually? A. The 2 items stretch evenly to fill the full 900px width, since 1fr expands them B. The 2 items stay near their 250px minimum width, and visible empty space remains where the unused third track would be, because auto-fill keeps that track reserved C. Grid throws an error because there aren't enough items to fill all computed columns D. The 2 items are centered and a third, empty placeholder box is rendered

B. The 2 items stay near their 250px minimum width, and visible empty space remains where the unused third track would be, because auto-fill keeps that track reserved — auto-fill preserves all computed tracks — including empty ones — as rigid placeholders. The 2 real items occupy their own tracks near the 250px minimum, while the third, itemless track still reserves its space, leaving a visible gap. auto-fit is what would collapse that empty track and let the items stretch.

Create a responsive product grid where each card is never narrower than 240px, cards stretch to fill remaining row width when there aren't enough to fill a row, and there's a consistent 20px gap..product-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 20px; }

You want stretching behavior for leftover space, so choose between auto-fit and auto-fill accordingly.