Masonry

A masonry grid that balances items across as many columns as the container can fit. It measures each item, so rows of uneven height pack tightly instead of leaving the gaps a plain CSS grid would.

order decides how items land in columns. balanced-stable (the default) sends each new item to the shortest column and never moves one that is already placed, which is what you want for feeds that append. balanced re-packs everything on every change for the tightest result, at the cost of items jumping around.

0
1
2
3
4
5
6
7
8
9
10
11
svelte<script lang="ts">
  import { Masonry, type MasonryOrder, order_options } from '$lib'

  let order = $state<MasonryOrder>(`balanced-stable`)
  let n_items = $state(12)
  const items = $derived(
    Array.from({ length: n_items }, (_, idx) => ({
      id: idx,
      // deterministic pseudo-random heights so the packing is visible but stable
      height: 40 + ((idx * 37) % 90),
    })),
  )
</script>

<label>
  Order
  <select bind:value={order}>
    {#each order_options as option (option)}<option>{option}</option>{/each}
  </select>
</label>
<label>
  Items <input type="number" min="1" max="40" bind:value={n_items} style="width: 4em" />
</label>

<Masonry {items} {order} minColWidth={120} gap={10} style="margin-top: 1em">
  {#snippet children({ item })}
    <div
      style="height: {item.height}px; display: grid; place-items: center; border-radius:
      4pt; background: var(--surface); border: 1px solid var(--border)"
    >
      {item.id}
    </div>
  {/snippet}
</Masonry>

Virtualization

Set virtualize with a height to render only the items near the viewport. Useful past a few hundred items, where the DOM node count starts to cost more than the measuring does.

<Masonry {items} virtualize height={600} overscan={5} />

Two things change while virtualizing, because off-screen items are never measured: order is forced to row-first, and the FLIP animation is switched off. Scroll position is driven entirely by getEstimatedHeight (default 150px), so the closer that is to your real item heights, the better the scrollbar behaves.