Difficulty: Intermediate
The grid-auto-flow property controls how grid items that are not explicitly placed are automatically positioned in the grid. By default, items flow in row order (left to right, then to the next row), but you can change this to column order or enable dense packing. Understanding auto-flow is essential for grids with dynamic content where you cannot predict the exact number of items.
The default value of grid-auto-flow is row, which places items left to right, filling each row before moving to the next. Setting grid-auto-flow: column changes the flow direction so items fill each column from top to bottom before moving to the next column. Column flow is useful for layouts like newspaper columns or vertical masonry-style grids where you want items to stack vertically first.
The dense keyword enables a backfilling algorithm. When auto-placing items, the grid normally leaves gaps when a larger item cannot fit in the current position. With grid-auto-flow: row dense (or just dense), the grid goes back and fills those gaps with smaller items that fit, even if it means placing them out of DOM order. This creates a more compact layout but can reorder items visually, which has accessibility implications similar to the order property in Flexbox.
Implicit tracks are rows or columns that the grid creates automatically when items are placed outside the explicit grid template. If you define grid-template-columns: repeat(3, 1fr) and have 10 items, the grid creates implicit rows for items 4-10. The size of implicit rows is controlled by grid-auto-rows, and implicit columns by grid-auto-columns. Without these properties, implicit tracks default to auto sizing (fitting their content). Always set grid-auto-rows when your grid has dynamic content.
The choice between CSS Grid and Flexbox depends on the layout requirements. Grid excels at two-dimensional layouts where you need to control both rows and columns simultaneously (page layouts, dashboards, image galleries). Flexbox excels at one-dimensional layouts where items flow in a single direction (navigation bars, button groups, card rows). In practice, most applications use both: Grid for the page-level structure and Flexbox for component-level layouts within grid cells. They can also be nested -- a grid item can be a flex container and vice versa.
/* Default: items fill rows left-to-right */
.row-flow {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 100px;
grid-auto-flow: row;
gap: 12px;
}
/* Items: 1 2 3
4 5 6
7 8 9 */
/* Column flow: items fill columns top-to-bottom */
.column-flow {
display: grid;
grid-template-rows: repeat(3, 100px);
grid-auto-columns: 1fr;
grid-auto-flow: column;
gap: 12px;
}
/* Items: 1 4 7
2 5 8
3 6 9 */
With row flow (default), items fill each row before moving to the next. With column flow, items fill each column first. Column flow requires defining grid-template-rows (or grid-auto-rows) instead of columns, and uses grid-auto-columns for implicit column sizes.
.gallery {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 150px;
grid-auto-flow: dense;
gap: 12px;
}
/* Some items span 2 columns */
.gallery-item-wide {
grid-column: span 2;
}
/* Some items span 2 rows */
.gallery-item-tall {
grid-row: span 2;
}
/* Without dense: gaps appear when wide items don't fit
[1] [wide-2 ] [ ] [3]
[4] [5] [6] [7]
With dense: small items backfill the gaps
[1] [wide-2 ] [3] [4]
[5] [6] [7] [8] */
Without dense, the grid leaves gaps when a wide item cannot fit at the end of a row. With dense, smaller items are pulled forward to fill those gaps, creating a tightly packed layout. The visual order may differ from the DOM order.
.dynamic-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
/* Explicit rows */
grid-template-rows: 200px 200px;
/* Implicit rows (items beyond row 2) */
grid-auto-rows: minmax(150px, auto);
gap: 16px;
}
/* Column flow with implicit columns */
.timeline {
display: grid;
grid-template-rows: repeat(4, auto);
grid-auto-flow: column;
grid-auto-columns: 300px;
gap: 20px;
overflow-x: auto;
}
grid-auto-rows sizes implicit rows created when items overflow the explicit grid. grid-auto-columns does the same for implicit columns. The timeline example creates a horizontally scrolling grid where each group of 4 items forms a column.
/* PAGE LAYOUT: Use Grid (two-dimensional) */
.page {
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"nav main"
"footer footer";
min-height: 100vh;
}
/* NAVBAR: Use Flexbox (one-dimensional) */
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
height: 60px;
}
/* CARD GRID: Grid for the grid, Flex inside each card */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
}
.card {
display: flex;
flex-direction: column;
}
.card-body {
flex: 1;
}
.card-footer {
margin-top: auto;
}
Use Grid for two-dimensional layouts (the page shell, the card grid). Use Flexbox for one-dimensional layouts within those grid cells (the navbar, the card internals). This combination leverages the strengths of both systems.
auto-flow dense, Implicit Tracks, grid-auto-flow, Grid vs Flexbox