CSS Native Masonry Layout: The End of JS Grid Libraries
Native CSS masonry finally replaces JavaScript grid libraries. grid-template-rows: masonry packs items into the shortest column—no JS, no layout thrashing.
Pinterest-style layouts have required JavaScript libraries since forever—Masonry.js, Isotope,
or rolling your own with ResizeObserver. CSS Grid came close but couldn't pack items
into the shortest column. Native CSS masonry is finally landing in browsers, and it's
simpler than you'd expect: grid-template-rows: masonry.
The Basic Syntax
Masonry integrates into CSS Grid. You define columns normally, then tell the rows to use the masonry algorithm:
.masonry-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-template-rows: masonry;
gap: 16px;
}
/* That's it. Items now flow into the shortest column. */
The masonry value on grid-template-rows tells the browser:
"Don't create strict row tracks. Instead, place each item in whichever column has
the most available space."
Columns vs Rows Masonry
You can apply masonry to either axis:
/* Column-based masonry (most common) */
.vertical-masonry {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: masonry; /* ← masonry axis */
}
/* Row-based masonry (horizontal flow) */
.horizontal-masonry {
display: grid;
grid-template-columns: masonry; /* ← masonry axis */
grid-template-rows: repeat(3, 100px);
}
The grid axis (the one without masonry) uses normal
Grid sizing. The masonry axis packs items using the algorithm.
How the Algorithm Works
For each item, the masonry algorithm:
- Finds the column with the most available space (shortest current height)
- Places the item at the top of that column's available space
- Updates the column's height tracker
- Moves to the next item
This is fundamentally different from Grid's normal behavior where items fill explicit row tracks. Masonry has no row tracks—just a growing, packed layout.
Spanning Multiple Columns
Grid spanning works on the grid axis, and masonry respects it:
.masonry-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: masonry;
gap: 16px;
}
/* Span 2 columns */
.wide-item {
grid-column: span 2;
}
/* Span all columns */
.full-width {
grid-column: 1 / -1;
}Spanning items are placed first (before masonry algorithm runs), then remaining items flow around them. This matches how Grid handles positioned items today.
Explicit Positioning Still Works
You can position items explicitly on the grid axis:
/* Pin to specific columns */
.pinned-left {
grid-column: 1; /* Always in first column */
}
.pinned-right {
grid-column: -2; /* Always in last column */
}
/* Span from column 2 to 4 */
.featured {
grid-column: 2 / 4;
}Explicitly positioned items are placed first. The masonry algorithm then fills remaining space with auto-placed items.
The display: masonry Debate
There's been significant debate about whether masonry should be:
- Part of Grid:
grid-template-rows: masonry(current spec) - Standalone display:
display: masonry(Chrome's proposal)
Chrome initially pushed for display: masonry as a separate layout mode,
arguing that masonry has fundamentally different behavior than Grid. Mozilla and
the CSS Working Group favored integration with Grid, since masonry reuses many Grid
concepts (columns, gaps, spanning).
/* Current spec (what's shipping) */
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: masonry;
}
/* Chrome's earlier proposal (NOT shipping) */
.container {
display: masonry;
masonry-template-tracks: repeat(3, 1fr);
}The Grid-integrated approach won. This means you get all Grid features (gap, alignment, spanning) without learning a new layout system.
Alignment Properties
Grid alignment properties work on the masonry axis too:
.masonry-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: masonry;
/* Align items within their grid area */
align-items: start; /* Default for masonry */
justify-items: center;
/* Distribute tracks */
justify-content: center;
}
The masonry axis doesn't have explicit tracks, so align-content and
align-tracks (proposed) have special behavior for distributing
space across columns.
Migration from JavaScript Libraries
Replacing Masonry.js or similar:
/* BEFORE: JavaScript library */
import Masonry from 'masonry-layout';
const grid = document.querySelector('.grid');
new Masonry(grid, {
itemSelector: '.grid-item',
columnWidth: 200,
gutter: 16
});
/* AFTER: Pure CSS */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-template-rows: masonry;
gap: 16px;
}
/* No JavaScript. No resize handlers. No layout thrashing. */Benefits of native masonry:
- No JavaScript bundle size (~25KB for Masonry.js)
- No layout recalculation on resize (browser handles it)
- No FOUC waiting for JS to calculate positions
- Works with lazy-loaded images (browser updates layout automatically)
The Image Loading Gotcha
One thing JavaScript libraries handled: waiting for images to load before calculating layout. Native masonry doesn't wait—it places items based on their current size.
/* Problem: Images load after initial layout */
<img src="large.jpg" /> /* Initially 0 height, then jumps */
/* Solution 1: Explicit aspect ratio */
.grid-item img {
aspect-ratio: 4 / 3;
width: 100%;
object-fit: cover;
}
/* Solution 2: CSS containment */
.grid-item {
contain: layout; /* Prevents layout shifts from affecting siblings */
}
/* Solution 3: content-visibility for below-fold items */
.grid-item {
content-visibility: auto;
contain-intrinsic-size: auto 300px;
}Always specify dimensions or aspect ratios on images in masonry layouts to prevent layout shifts as content loads.
Browser Support and Fallbacks
As of early 2026:
- Firefox: Supported (shipped first, behind flag since 2020)
- Safari: Supported (17.4+)
- Chrome/Edge: Supported (recently landed)
For older browsers, the fallback is clean—they get regular Grid layout:
.masonry-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-template-rows: masonry; /* Ignored in old browsers */
gap: 16px;
}
/* Old browsers: regular grid with equal row heights
New browsers: masonry packed layout */
The masonry value is simply ignored by browsers that don't understand it,
leaving you with a perfectly usable (if less elegant) grid.
Performance Characteristics
Native masonry is significantly faster than JavaScript solutions because:
- Layout calculated once per frame in the browser's layout engine
- No JavaScript execution or DOM measurements
- Compositor-friendly (no forced synchronous layouts)
- Automatic optimization for resize and content changes
However, very large masonry grids (thousands of items) can still cause layout
performance issues. Consider content-visibility: auto for
virtualization-like benefits.
Key Takeaways
grid-template-rows: masonryenables Pinterest-style layouts- Works with existing Grid features: gaps, spanning, alignment
- Column masonry (vertical flow) is most common; row masonry (horizontal) also works
- Graceful fallback to regular Grid in unsupported browsers
- Always set image dimensions/aspect-ratios to prevent layout shifts
- Replaces ~25KB JavaScript libraries with zero-JS performance
Advertisement
Explore these curated resources to deepen your understanding
Official Documentation
Tools & Utilities
Related Insights
Explore related edge cases and patterns
Advertisement