How to create a masonry layout like Pinterest?
Build a Pinterest-style masonry layout with CSS columns, or with CSS Grid and a short script, and learn which one to pick when the order of items matters.
The quickest way is CSS multi-column layout. Put the items in a container with columns, and the browser flows them into columns of different heights, with no JavaScript:
<div class="gallery">
<figure class="gallery-item">
<img src="/photos/forest.jpg" alt="Foggy forest"
width="600" height="800">
</figure>
<!-- more items -->
</div>.gallery {
columns: 240px;
column-gap: 16px;
}
.gallery-item {
break-inside: avoid;
margin: 0 0 16px;
}
.gallery-item img {
display: block;
width: 100%;
height: auto;
}columns: 240px creates as many columns as fit, each roughly 240px wide or wider. columns: 3 240px does the same but never makes more than three. break-inside: avoid stops an item from being split between two columns, and the margin reset matters because <figure> has default margins.
The catch: the order goes down, not across
Columns fill from top to bottom. The first few items go into the first column, the next ones into the second, and so on, so item 2 ends up under item 1 instead of next to it. Resizing the window or adding more items (for example with infinite scroll) moves items from one column to another.
For a photo gallery, nobody notices. For a feed sorted by date, it's confusing, because the newest posts don't read left to right.
Grid and JavaScript: order that reads across
Make a grid with very short rows and let every item span as many rows as its height needs. Grid places the items row by row, so the order reads roughly left to right:
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 16px;
}
/* Added by the script, so the grid still works without it */
.gallery.is-masonry {
grid-auto-rows: 4px;
row-gap: 0;
}
.gallery-item {
align-self: start;
margin: 0;
}align-self: start keeps each item at the height of its content instead of stretching it to its grid area, so the script measures the real height. The vertical gap is added to each item's span instead of using row-gap. Keep the img rule from the columns version.
const gallery = document.querySelector('.gallery');
const ROW_HEIGHT = 4; // same as grid-auto-rows
const GAP = 16;
function layout() {
const items = [...gallery.children];
// Read all heights first, then write, to avoid extra layouts
const spans = items.map((item) => {
const { height } = item.getBoundingClientRect();
return Math.ceil((height + GAP) / ROW_HEIGHT);
});
items.forEach((item, i) => {
item.style.gridRowEnd = `span ${spans[i]}`;
});
}
gallery.classList.add('is-masonry');
const observer = new ResizeObserver(layout);
for (const item of gallery.children) {
observer.observe(item);
}The ResizeObserver runs layout when an item changes size: when an image loads, when the columns get wider or narrower on resize, and once at the start. Call observer.observe() for new items when you append them. Width and height attributes on the images give the items their final height before the files load, so the layout doesn't jump.
Native CSS masonry is on its way
A masonry mode for CSS Grid is being standardized, and browsers have experimented with it under different syntaxes. You'll see grid-template-rows: masonry and display: grid-lanes in articles and demos. At the time of writing it isn't supported across browsers, and the syntax has changed along the way, so treat it as an experiment and check Can I Use before relying on it. Once it lands everywhere, it will do what the grid and script version does, without the script.
The Masonry library
The Masonry library (masonry-layout on npm) has been around for years. It positions every item absolutely and calculates the layout in JavaScript, and it has options like animated re-layouts. For most sites, one of the two approaches above is less code.
Which one to use
- The order doesn't matter (photos, inspiration boards, portfolios): use CSS columns.
- The order matters (newest first, search results, infinite scroll): use the grid with the script, or a library.
- When native masonry is supported in the browsers you care about, switch to it.