A CSS grid sets three 200px columns and grid-template-rows: 100px 100px, but nine items are placed in it. The third row hugs its content instead of being 100px tall — why, and which property fixes it?
answer
- count items against declared rows
- the list does not repeat itself
- a different property owns row three
- auto means sized by content
- grid-auto-rows, optionally with minmax()
basics
~20 sThe third row is implicit: grid-template-rows declared only two, so the browser created the third to hold the overflow. Implicit tracks default to grid-auto-rows: auto, which sizes to content. Set grid-auto-rows: 100px to fix it.
solid answer
~40 sNine items in three columns need three rows, but `grid-template-rows` only declared two, so the third is an implicit row the browser created during placement. Sizes in `grid-template-rows` apply position-by-position to the explicit rows only — the list does not repeat and does not extend. Implicit rows are sized by `grid-auto-rows`, whose initial value is `auto`, so the row sizes to its tallest content and looks wrong next to two fixed 100px rows. The fix is `grid-auto-rows: 100px`, which applies to every generated row. In real code I would usually write `grid-auto-rows: minmax(100px, auto)` so generated rows have a floor of 100px but can still grow if the content genuinely needs more room.
code
css · 7 lines.grid {
display: grid;
grid-template-columns: 200px 200px 200px;
grid-template-rows: 100px 100px;
grid-auto-rows: minmax(100px, auto);
gap: 16px;
}go deeper
Recognise that a row you did not declare was created for the extra items, and know that grid-auto-rows is where you give it a height.
Explain that grid-template-rows is positional and stops at its last value, that implicit rows default to auto and therefore hug content, and how the grid-auto-rows list cycles.
Argue for minmax(100px, auto) over a hard height so long or translated content does not overflow, and question whether the explicit rows should have been declared at all for data-driven counts.
Treat the mixed explicit/implicit seam as a design-system risk: a hardcoded row count encodes an assumption about data volume that no component contract states, and it fails silently in both directions.
## What actually happened Three columns and nine items means three rows are needed. `grid-template-rows: 100px 100px` describes two. The browser cannot drop the last three items, so it creates a third row — an implicit row — and puts them there. Everything about that row is normal except its size, which comes from a different property than you expect. ```css .grid { display: grid; grid-template-columns: 200px 200px 200px; grid-template-rows: 100px 100px; /* rows 1 and 2 only */ /* row 3 exists, but nothing here sizes it */ } ``` ## Why grid-template-rows does not cover it A common mental model is that a track list is a repeating pattern — write two sizes and rows 3, 5, 7 get the first size and rows 4, 6 get the second. That is not how it works. `grid-template-rows` is positional: the first value sizes row 1, the second sizes row 2, and the list ends there. Rows beyond the list are not part of the explicit grid at all, so nothing in that declaration can reach them. ## The property that does cover it `grid-auto-rows` sizes implicit rows, and its initial value is `auto`. An `auto` track sizes to its content: its height is driven by the content contributions of the items inside it. So a 100px, 100px, content-height stack is exactly what the default produces. ```css .grid { grid-template-rows: 100px 100px; grid-auto-rows: 100px; /* every generated row is 100px */ } ``` That single line makes all three rows equal. Note the asymmetry that trips people up in review: the explicit sizes are written twice and the implicit size once, because one is a list of specific tracks and the other is a rule for all future tracks. ## The cycling list `grid-auto-rows` takes one or more track sizes, and when you give more than one the pattern repeats across the generated rows: ```css .grid { grid-auto-rows: 80px 160px; } /* implicit rows: 80px, 160px, 80px, 160px, ... */ ``` It is a list of track sizes, not a template, so `repeat()` is not valid there — you write the cycle out and it repeats on its own. ## The version you actually ship A hard `grid-auto-rows: 100px` makes rows uniform, but it also clips nothing and hides nothing: content taller than 100px simply spills out of its cell unless the item itself scrolls. For user-generated or translated content that is a real risk, so the production form is usually a floor with room to grow: ```css .grid { grid-auto-rows: minmax(100px, auto); } ``` Read that as: at least 100px, more if the content needs it. You keep the visual rhythm of the design in the common case and stay safe when a card carries three lines of text instead of one. ## Should the rows have been explicit at all? Often not. If nine items came from an API, they might be six tomorrow and fourteen next week, and `grid-template-rows: 100px 100px` is a guess that will be wrong most of the time. Declaring only the columns and letting every row be implicit is simpler and more honest: ```css .grid { display: grid; grid-template-columns: 200px 200px 200px; grid-auto-rows: minmax(100px, auto); gap: 16px; } ``` Now there is no seam where explicit rows stop and implicit ones start, so the bug in the question cannot occur. The mixed form is worth keeping only when the first rows genuinely differ — a taller hero row followed by uniform ones, for example — and then the mismatch between the two properties is intentional rather than an accident. ## How to diagnose this in the wild In devtools' grid overlay, the row line numbers keep counting past the tracks you declared, which is the tell that you are looking at implicit tracks. The quick check is arithmetic: count the items, divide by the number of columns, round up, and compare with the length of your `grid-template-rows` list. Any excess rows are the browser's, and only `grid-auto-rows` speaks to them. ## What to say out loud Name the row as implicit, name `grid-auto-rows` as the property that sizes implicit rows, name `auto` as its initial value, and then add the judgment: for content of unknown length, prefer declaring one axis and sizing the other with `grid-auto-rows` rather than guessing at a row count.
- Why not just write grid-template-rows: 100px 100px 100px once you see three rows are needed?Because the item count is usually data-driven. Adding a third value fixes today's render and breaks on the tenth item, and if the list ever shrinks you are left with an empty declared row that still consumes 100px plus its gap. A rule that covers all generated rows survives both directions.
- What does grid-auto-rows: 100px 200px mean?It gives a repeating pattern for implicit rows: the first generated row is 100px, the second 200px, the third 100px again, and so on. It is a cycling list of track sizes rather than a template, so `repeat()` has no place in it.
- If I set grid-auto-rows: 100px and an item's content is 300px tall, what happens?The row stays 100px and the content overflows its cell, painting over whatever sits below unless the item establishes its own scroll container. That is exactly why `minmax(100px, auto)` is the safer default — it keeps the floor without capping the ceiling.
saying these in an interview costs you the question
- Thinks the grid-template-rows list repeats for later rows
- Blames a missing height on the items rather than the track
- Tries to fix it by adding more grid-template-rows values
- Says the third row is explicit because rows were declared
- Confuses grid-auto-rows with grid-auto-flow