Subgrid & Grid Lanes
When you need nested grids to line up, plus native masonry.
Once you can spin up a Grid in your sleep, you start running into problems that need the deeper toolkit: nested grids that need to align with their parent, Pinterest-style masonry, weirdly shaped overflow, and the never-ending question of "where did this extra row come from?" This lesson covers the advanced features that turn Grid from useful into magical.
Explicit vs implicit grid
Everything you write in grid-template-columns and grid-template-rows is the explicit grid. The moment you place an item beyond those tracks, the browser creates the implicit grid. Its tracks are sized by grid-auto-columns and grid-auto-rows, which default to auto (meaning "fit content").
.list {
display: grid;
grid-template-columns: 1fr 1fr; /* explicit: two columns */
grid-auto-rows: 100px; /* implicit rows are all 100px */
gap: 8px;
}Drop fifty children into that container and you get twenty-five rows, each 100px tall. You never declared the rows. Grid created them because items needed cells.
Dense packing fills the holes
Normally Grid places items in source order. If item three is too wide to fit in row one, Grid leaves a hole and puts it in row two. The dense keyword on grid-auto-flow changes that: Grid will go back and fill earlier holes with later items that fit.
.gallery {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-flow: dense; /* fill any earlier holes */
gap: 12px;
}grid-auto-flow: dense can scramble the visual order relative to the DOM order. Screen readers still announce in DOM order, so don't use it for content that depends on sequence (like a numbered list or a step-by-step tutorial). Great for image galleries, dangerous for articles.Subgrid: nested alignment that actually aligns
Subgrid is one of the most-requested features in CSS history, and it finally landed in every evergreen browser by 2023. Without subgrid, a nested grid has its own track sizing that doesn't know about the parent. With subgrid, the child grid inherits the parent's tracks. Headers, body, and footers in a row of cards can line up perfectly even if each card has different content.
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto 1fr auto; /* header / body / footer */
gap: 16px;
}
.card {
display: grid;
grid-row: span 3; /* take all three rows */
grid-template-rows: subgrid; /* inherit parent's row sizing */
}Now every card's header sits on the parent's first row, body on the second, footer on the third. If one card has a long title, every card's header grows together. No JavaScript, no equal-height hacks.
Grid Lanes: masonry, after the naming fight
Masonry layout (uneven rows, vertically packed columns) used to require a JavaScript library. It took the CSS Working Group years to agree on how to express it, and if you learned the early proposal you learned the wrong thing. The answer is not grid-template-rows: masonry. It is a new display value called grid-lanes. Safari 26.4 shipped it in March 2026; Chrome, Edge, and Firefox have it behind a flag.
.pinboard {
display: grid-lanes;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 12px;
}That is the whole thing. You define the lanes with the grid-template-* properties you already know, and the browser drops each item into whichever lane gets it closest to the top.
Direction comes from which axis you define
Define columns and you get the classic waterfall. Define rows instead and items stack left to right like bricks. A new normal value for grid-auto-flow works this out for you, so you do not set a direction property at all.
/* waterfall: items fall down columns */
.waterfall { display: grid-lanes; grid-template-columns: 1fr 1fr 1fr 1fr; }
/* brick: items flow across rows */
.brick { display: grid-lanes; grid-template-rows: 1fr 1fr 1fr; }Tolerance
Strict packing has an ugly side effect. If one item is two pixels shorter than its neighbour, the next item dives into that lane to gain two pixels, and the visual grouping of rows falls apart. Tolerance tells the algorithm how much height difference to ignore before it bothers switching lanes. Raise it and items line up in looser rows; set it to zero and you get the strictest possible packing.
flow-tolerance, and in August 2026 the CSS Working Group resolved to rename it fit-tolerance. The new name is not in the spec text or in any browser yet. If you use it now, expect to change one identifier later, and do not be surprised when the two names both show up in search results.Because support is still partial, gate it behind a feature query. A plain grid is a reasonable fallback: even columns, even rows, no packing.
.pinboard {
display: grid; /* fallback for browsers without grid-lanes */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 12px;
}
@supports (display: grid-lanes) {
.pinboard { display: grid-lanes; }
}Subgrid in action
Three cards, each with a title, body, and footer. Each card has different content length, but everything lines up because we use subgrid. Try removing the subgrid line and watch the alignment fall apart.
Naming areas with subgrid
Subgrid can inherit named lines from the parent. Combined with grid-template-areas, you get layouts where parents and children share a vocabulary, and you only define alignment in one place. This becomes essential in design systems where dozens of card variants all need to align identically.
Anti-pattern: 100% width children
New Grid users sometimes set width: 100% on grid children, which is redundant. Grid already gives the child the cell width. Adding width: 100% can cause overflow when the child has padding. Trust the cell.
Quiz
What does grid-auto-flow: dense do?
Recap
- Explicit grid: what you declare. Implicit grid: what Grid generates when items spill past your tracks.
grid-auto-flow: densebackfills holes. Great for galleries, bad for content order.- Subgrid inherits track sizing from the parent so nested layouts align perfectly.
- Native masonry is
display: grid-lanes, notgrid-template-rows: masonry. Safari 26.4 ships it; elsewhere it is behind a flag, so wrap it in@supports (display: grid-lanes). flow-tolerancecontrols how strictly items pack, which is how you keep rough rows from breaking up.- Don't set
width: 100%on grid children. The cell already does that.