Svelte
December 4, 2025 • 2 min read

Pinterest-style masonry grid in Svelte 5 runes mode

A quick and minimal setup to get a Pinterest-style Masonry grid running in Svelte 5.

Pinterest-style masonry grid in Svelte 5 runes mode
Table of contents
Table of contents

If you’re a UX nerd, you probably love a good staggered grid view. It’s super common in mobile development but kinda overlooked on the web — maybe because it’s such a hassle to implement properly. Even though the experimental stuff is ongoing (you can check it out here: masonry-update), most people still avoid it.

Personally, I love this kind of grid because once you have it in your app, your app stops annoying users by limiting the picture size they can upload. Users can throw in whatever weird aspect ratio they want and the grid will still render beautifully. Basically looks like Pinterest.

In this post, I’m writing the minimum setup for using the masonry-layout package by David DeSandro in Svelte 5 runes mode. The whole component is around 35 lines — nothing crazy.

Packages

Before jumping into the Svelte part, you gotta install the Masonry package first:

npm i -D masonry-layout

And if you’re using TypeScript (which I am), also install the types:

npm i -D @types/masonry-layout

Component

Here’s the code snippet from my MasonryLayout.svelte component, plus the usage example below it.

<script lang="ts">
import { onMount } from 'svelte'

let { items }: { items: string[] } = $props()
let msnryEl = $state<HTMLElement | null>(null)
let msnryWidth = $state(0)

// 2 columns on mobile, 3 on larger screens
let columnWidth = $derived(msnryWidth < 640 ? msnryWidth / 2 : msnryWidth / 3)

const initMsnryWidth = () => {
  const rect = msnryEl?.getBoundingClientRect()
  msnryWidth = rect?.width || 0
}

const initMasonryLayout = async () => {
  const Masonry = (await import('masonry-layout')).default
  new Masonry(msnryEl!, {
    itemSelector: '.grid-item',
    columnWidth: columnWidth,
    horizontalOrder: true
  })
}

const onResize = () => {}

onMount(() => {
  if (msnryEl) {
    initMsnryWidth()
    initMasonryLayout()
  }
})
</script>

<svelte:window onresize={onResize} />

<ul class="w-full" bind:this={msnryEl}>
  {#each items as item}
    <li class="grid-item p-1" style={`width: ${columnWidth}px;`}>
      <img src={item} alt="" />
    </li>
  {/each}
</ul>

Usage

And here’s how you use MasonryLayout.svelte in your Svelte page:

<script lang="ts">
import MasonryLayout from '$lib/components/MasonryLayout.svelte'

let images = [
  'https://images.pexels.com/photos/34533776/pexels-photo-34533776.jpeg',
  'https://images.pexels.com/photos/33773909/pexels-photo-33773909.jpeg',
  'https://images.pexels.com/photos/31740993/pexels-photo-31740993.jpeg',
  'https://images.pexels.com/photos/29531010/pexels-photo-29531010.jpeg',
  'https://images.pexels.com/photos/17567462/pexels-photo-17567462.jpeg',
  'https://images.pexels.com/photos/34642333/pexels-photo-34642333.jpeg',
  'https://images.pexels.com/photos/34517503/pexels-photo-34517503.png'
]
</script>

<div class="m-4">
  <MasonryLayout items={images} />
</div>

That’s all for this one — see ya!