Many CSS consultants have weighed closely on potential syntaxes for a brand new masonry format characteristic final 12 months. There have been two important camps and a third camp that strikes a steadiness between the 2:
- Use
show: masonry
- Use
grid-template-rows: masonry
- Use
item-pack: collapse
I don’t suppose they’ve got here up with a decision but. However you may need to know that Firefox already helps the masonry format with the second syntax. And Chrome is testing it with the primary syntax. Whereas it’s cool to see native assist for CSS Masonry evolving, we are able to’t actually use it in manufacturing if different browsers don’t assist the identical implementation…
So, as an alternative of including my voice to a kind of camps, I went on to determine how make masonry work right now with different browsers. I’m pleased to report I’ve discovered a method — and, bonus! — that assist might be offered with solely 66 strains of JavaScript.
On this article, I’m gonna present you the way it works. However first, right here’s a demo so that you can play with, simply to show that I’m not spewing nonsense. Observe that there’s gonna be a slight delay since we’re ready for a picture to load first. If you happen to’re putting a masonry on the high fold, contemplate skipping together with photos due to this!
Anyway, right here’s the demo:
What within the magic is that this?!
Now, there are a ton of issues I’ve included on this demo, though there are solely 66 strains of JavaScript:
- You’ll be able to outline the masonry with any variety of columns.
- Every merchandise can span a number of columns.
- We await media to load earlier than calculating the dimensions of every merchandise.
- We made it responsive by listening to modifications with the
ResizeObserver
.
These make my implementation extremely strong and prepared for manufacturing use, whereas additionally far more versatile than many Flexbox masonry knockoffs on the market on the interwebs.
Now, a scorching tip. If you happen to mix this with Tailwind’s responsive variants and arbitrary values, you possibly can embrace much more flexibility into this masonry grid with out writing extra CSS.
Okay, earlier than you get puffed up any additional, let’s come again to the primary query: How on earth does this work?
Let’s begin with a polyfill
Firefox already helps masonry layouts through the second camp’s syntax. Right here’s the CSS you want to create a CSS masonry grid format in Firefox.
.masonry {
show: grid;
grid-template-columns: repeat(
auto-fit,
minmax(min(var(--item-width, 200px), 100%), 1fr)
);
grid-template-rows: masonry;
grid-auto-flow: dense; /* Elective, however really useful */
}
Since Firefox already has native masonry assist, naturally we shouldn’t fiddle with it. One of the best ways to test if masonry is supported by default is to test if grid-template-rows
can maintain the masonry
worth.
perform isMasonrySupported(container) {
return getComputedStyle(container).gridTemplateRows === 'masonry'
}
If masonry is supported, we’ll skip our implementation. In any other case, we’ll do one thing about it.
const containers = doc.querySelectorAll('.masonry')
containers.forEach(async container => {
if (isMasonrySupported(container)) return
})
Masonry format made easy
Now, I need to preface this phase that I’m not the one who invented this system.
I found out this system after I was digging by means of the net, trying to find potential methods to implement a masonry grid right now. So kudos goes to the unknown developer who developed the concept first — and maybe me for understanding, changing, and utilizing it.
The approach goes like this:
- We set
grid-auto-rows
to0px
. - Then we set
row-gap
to1px
. - Then we get the merchandise’s peak by means of
getBoundingClientRect
. - We then measurement the merchandise’s “row allocation” by including the
peak
thecolumn-gap
worth collectively.
That is actually unintuitive in the event you’ve been utilizing CSS Grid the usual method. However when you get this, you can too grasp how this works!
Now, as a result of that is so unintuitive, we’re gonna take issues step-by-step so that you see how this complete factor evolves into the ultimate output.
Step-by-step
First, we set grid-auto-rows
to 0px
. That is whacky as a result of each grid merchandise will successfully have “zero peak”. But, on the similar time, CSS Grid maintains the order of the columns and rows!
containers.forEach(async container => {
// ...
container.fashion.gridAutoRows="0px"
})

Second, we set row-gap
to 1px
. As soon as we do that, you start to note an preliminary stacking of the rows, each one pixel beneath the earlier one.
containers.forEach(async container => {
// ...
container.fashion.gridAutoRows="0px"
container.fashion.setProperty('row-gap', '1px', 'necessary')
})

Third, assuming there are not any photos or different media components within the grid objects, we are able to simply get the peak of every grid merchandise with getBoundingClientRect
.
We are able to then restore the “peak” of the grid merchandise in CSS Grid by substituting grow-row-end
with the peak
worth. This works as a result of every row-gap
is now 1px
tall.
After we do that, you possibly can see the grid starting to take form. Every merchandise is now (kinda) again at their respective positions:
containers.forEach(async container => {
// ...
let objects = container.youngsters
format({ objects })
})
perform format({ objects }) {
objects.forEach(merchandise => {
const ib = merchandise.getBoundingClientRect()
merchandise.fashion.gridRowEnd = `span ${Math.spherical(ib.peak)}`
})
}

We now want to revive the row hole between objects. Fortunately, since masonry grids normally have the identical column-gap
and row-gap
values, we are able to seize the specified row hole by studying column-gap
values.
As soon as we try this, we add it to grid-row-end
to broaden the variety of rows (the “peak”) taken up by the merchandise within the grid:
containers.forEach(async container => {
// ...
const objects = container.youngsters
const colGap = parseFloat(getComputedStyle(container).columnGap)
format({ objects, colGap })
})
perform format({ objects, colGap }) {
objects.forEach(merchandise => {
const ib = merchandise.getBoundingClientRect()
merchandise.fashion.gridRowEnd = `span ${Math.spherical(ib.peak + colGap)}`
})
}

And, identical to that, we’ve made the masonry grid! Every thing from right here on is just to make this prepared for manufacturing.
Ready for media to load
Strive including a picture to any grid merchandise and also you’ll discover that the grid breaks. That’s as a result of the merchandise’s peak will likely be “fallacious”.

It’s fallacious as a result of we took the peak
worth earlier than the picture was correctly loaded. The DOM doesn’t know the scale of the picture but. To repair this, we have to await the media to load earlier than operating the format
perform.
We are able to do that with the next code (which I shall not clarify since this isn’t a lot of a CSS trick 😅):
containers.forEach(async container => {
// ...
attempt {
await Promise.all([areImagesLoaded(container), areVideosLoaded(container)])
} catch(e) {}
// Run the format perform after photos are loaded
format({ objects, colGap })
})
// Checks if photos are loaded
async perform areImagesLoaded(container) {
const photos = Array.from(container.querySelectorAll('img'))
const guarantees = photos.map(img => {
return new Promise((resolve, reject) => {
if (img.full) return resolve()
img.onload = resolve
img.onerror = reject
})
})
return Promise.all(guarantees)
}
// Checks if movies are loaded
perform areVideosLoaded(container) {
const movies = Array.from(container.querySelectorAll('video'))
const guarantees = movies.map(video => {
return new Promise((resolve, reject) => {
if (video.readyState === 4) return resolve()
video.onloadedmetadata = resolve
video.onerror = reject
})
})
return Promise.all(guarantees)
}
Voilà, now we have a CSS masnory grid that works with photos and movies!

Making it responsive
It is a easy step. We solely want to make use of the ResizeObserver API to pay attention for any change in dimensions of the masonry grid container.
When there’s a change, we run the format
perform once more:
containers.forEach(async container => {
// ...
const observer = new ResizeObserver(observerFn)
observer.observe(container)
perform observerFn(entries) {
for (const entry of entries) {
format({colGap, objects})
}
}
})
This demo makes use of the usual Resize Observer API. However you may make it less complicated through the use of the refined resizeObserver
perform we constructed the opposite day.
containers.forEach(async container => {
// ...
const observer = resizeObserver(container, {
callback () {
format({colGap, objects})
}
})
})
That’s just about it! You now have a strong masonry grid that you should utilize in each working browser that helps CSS Grid!
Thrilling, isn’t it? This implementation is so easy to make use of!
Masonry grid with Splendid Labz
If you happen to’re not adversarial to utilizing code constructed by others, perhaps you may need to contemplate grabbing the one I’ve constructed for you in Splendid Labz.
To do this, set up the helper library and add the required code:
# Putting in the library
npm set up @splendidlabz/kinds
/* Import all layouts code */
@import '@splendidlabz/layouts';
// Use the masonry script
import { masonry } from '@splendidlabz/kinds/scripts'
masonry()
One very last thing: I’ve been constructing a ton of instruments to assist make net growth a lot simpler for you and me. I’ve parked all of them beneath the Splendid Labz model — and certainly one of these examples is that this masonry grid I confirmed you right now.
If you happen to love this, you is likely to be occupied with different format utilities that makes format tremendous easy to construct.
Now, I hope you could have loved this text right now. Go unleash your new CSS masonry grid in the event you want to, and all the perfect!