EdgeCases Logo
Feb 2026
CSS
Deep
7 min read

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.

css
masonry
grid
layout
css-grid
performance

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:

  1. Finds the column with the most available space (shortest current height)
  2. Places the item at the top of that column's available space
  3. Updates the column's height tracker
  4. 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: masonry enables 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

Related Insights

Explore related edge cases and patterns

CSS
Surface
CSS text-wrap: balance and pretty
6 min
CSS
Surface
CSS contain: Render Isolation for Performance
6 min

Advertisement