← blog
CSSOctober 30, 2026 · 6 min read

Stop Using a JS Masonry Library. CSS Grid Lanes Does It Natively.

Masonry layouts have meant a JS library or a CSS column-count hack that scrambles reading order ever since Pinterest made the look popular. CSS Grid Lanes finally does it natively — here's the real support story and how to ship it today.

Parsa Jiravand · Frontend engineer · building bestpractic
Stop Using a JS Masonry Library. CSS Grid Lanes Does It Natively.

Open any "masonry" npm package's GitHub issues and you'll find the same complaint, filed every few months in different words: items jump on resize, SSR renders everything in one column until JS hydrates, and the bundle is 15–40KB for something that is, conceptually, "pack boxes into the shortest column." It's a layout problem. It has needed a JavaScript library to solve because CSS Grid — for all its power — has never been able to do it. Every row in a CSS grid has to be the same height across every column. A masonry layout is defined by not having that constraint.

So most teams land on one of two compromises. Neither is actually masonry.

The CSS-only version looks like this, and it's seductive because it needs zero JavaScript:

CSS
1
2
3
4
5
6
7
8
9
.gallery { columns: 3; column-gap: 16px; } .gallery > * { break-inside: avoid; margin-bottom: 16px; }

It renders a Pinterest-ish grid. Ship it, move on — until someone tabs through the gallery with a keyboard, or a screen reader user navigates it, and the order makes no sense. columns fills the first column top-to-bottom before it starts the second, so if your 12 items are numbered 1–12 in the markup, the visual layout reads 1, 2, 3, 4 down the left column, then 5, 6, 7, 8 down the middle, then 9–12 on the right — not left-to-right the way a grid of cards normally reads, and not the order a screen reader announces it in either. Nobody notices in a quick visual QA pass. It shows up as "why does the keyboard focus jump to the bottom of the page and then back to the middle" three sprints later.

The other common fix is a JS library (masonry-layout, react-masonry-css, or rolling your own ResizeObserver + "find the shortest column, append there" loop). That one gets the packing right, but it pays for it: a layout pass that runs on every resize and every image load, a flash of an unstyled single column before JS hydrates, and a dependency for something that feels like it should be CSS's job.

It should be. As of 2026, in one browser, it finally is.

CSS Grid lays out in two full passes: define the tracks (rows and columns), then place items into cells that already have a fixed size. That's precisely why Grid can't do masonry on its own — masonry is "don't fix the row heights, just pack each item into whichever column currently has the most room." The CSS Working Group spent years on this as "Grid Level 3," tried a Firefox-only grid-template-rows: masonry syntax behind a flag, and in January 2026 settled on a cleaner final design: a new display value, grid-lanes (and its inline counterpart, inline-grid-lanes), instead of overloading an existing grid property.

CSS
1
2
3
4
5
.gallery { display: grid-lanes; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }

That's the whole layout. You still declare grid-template-columns exactly like ordinary Grid — fixed count, auto-fill, minmax(), all of it. The difference is what the browser does with your items: instead of requiring every row to line up across columns, it walks them in DOM order and drops each one into whichever lane (column) currently has the least content. That's a real improvement over the columns hack's worst case — which deterministically exhausts column 1 before column 2 even starts — but it's not a complete guarantee. Because lane heights can drift unevenly, tab order can still zigzag across lanes for some content mixes (DOM item 4 landing, say, in the lane that item 6 "should" occupy). The spec gives you a flow-tolerance property to dampen how far an item is allowed to jump lanes, and a full fix needs a separate, still-experimental property (reading-flow) that no browser currently wires into grid-lanes. What you get today is markup order that usually tracks visual order far more closely than the columns hack ever did — not a watertight promise.

Runs right in your browser — poke at it and watch the concept react live.

Ordinary Grid placement still works on top of it. An item can jump several lanes with the same syntax you already use:

CSS
1
2
3
.gallery .featured { grid-column: span 2; }

This is the part most "masonry is here!" posts skip. As of this writing, Safari is the only browser shipping display: grid-lanes unflagged — it landed in the stable Safari 26.4 release. Chrome and Firefox both have implementations, but both ship them disabled by default, behind an experimental-platform-features flag. There's no date yet for either to flip that switch. If you write display: grid-lanes into production CSS today, the vast majority of your visitors get whatever your fallback declares — not masonry.

That's not a reason to wait. It's a reason to use @supports the way it was built for:

CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
.gallery { /* Fallback: plain grid, every item the same visual weight. Not masonry, but DOM order stays reading order. */ display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; } @supports (display: grid-lanes) { .gallery { display: grid-lanes; } }

Visitors on Safari 26.4+ get real masonry today. Everyone else gets a perfectly readable uniform grid instead of a broken one — not the layout you want long-term, but never a layout that embarrasses you. When Chrome and Firefox unflag it, your fallback quietly becomes dead code you can delete, with zero migration.

Don't rip out a masonry library the moment you read this. Keep it if you need:

  • Masonry everywhere today, with no visually-degraded fallback acceptable on Chrome or Firefox.
  • Re-flowing based on content you fetch after layout in a way your current library already handles well (infinite scroll galleries with dynamic image dimensions).
  • Fine control over packing order beyond "shortest lane wins" — some libraries support column-balancing heuristics grid-lanes doesn't expose.

For a new project, or the next redesign of an existing gallery, ship the @supports version above. You get the real thing where it's supported and a safe grid everywhere else, for four lines of CSS and zero kilobytes of JavaScript.

Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.

columns: N was never masonry — it was a print-style multi-column reflow that happened to look like masonry until you checked tab order. The JS libraries got the packing right but paid for it in bundle size and a hydration flash. display: grid-lanes is CSS finally modeling what Pinterest-style layout actually is — a placement algorithm, not a side effect — and @supports means you don't have to choose between "ship it now" and "ship it correctly everywhere."

Are you going to drop display: grid-lanes into a gallery today behind @supports, or wait for Chrome and Firefox to unflag it first? Tell me which, and why, in the comments.


🚀 Want more like this? Every guide, playground, and quiz lives on bestpractic.org — open it and sign up free so the next one finds you.

Thanks for reading! Let's stay connected:

Keep reading

One post a day, in your inbox

Each one with a runnable playground and a quiz. No pitch, no digest, unsubscribe in one click.

0 comments

Sign in to join the discussion, like comments, and save articles for later.