A masonry/bento grid library for Vue 3, React, and Nuxt — cards of different heights pack themselves into balanced columns, with wide bento cards, smooth reflow animation, and support for large lists.

A grid of cards with naturally differing heights usually means either CSS columns (which fills top-to-bottom, then the next column — order rarely matches the visual reading order) or a heavier masonry library with its own opinions about markup. Masonry Kit measures your existing cards and packs them, leaving the markup and styling entirely yours.
Cards of naturally different heights need packing into visually balanced columns — skyline packing places each card in the least-filled column, instead of CSS columns' arbitrary fill order.
Some cards are wider than others — via colSpan/rowSpan they take part in the same skyline pack as the regular ones, instead of being hand-placed in a CSS Grid template that needs updating every time the data changes.
Virtualization keeps relayout cheap by estimating off-screen items instead of measuring the entire list on every scroll.
A CSS columns approximation renders before and during hydration, then swaps to the exact layout the instant the engine actually measures the cards — in the same frame, no visible jump.

Classic shortest-lane masonry packing, extended to bento-style colSpan/rowSpan cards from the first line of code, not bolted on afterward. 'balanced' (default) picks the least-filled lane group for each item, 'ordered' keeps strict round-robin instead.

direction: 'vertical' (columns) and 'horizontal' (rows) share the same core geometry, not two separate algorithms to keep in sync.

Every item is measured via ResizeObserver and placed with transform: translate(), so a reflow never triggers layout of its own — and an image's height changing after load re-packs the grid automatically.

A two-phase layout: items outside the visible range (± overscan) skip real DOM measurement entirely and use an estimated size instead, so relayout stays cheap regardless of list size.

Items slide into their new slot instead of teleporting, and fade in/out on add/remove, entirely via inline styles — zero required CSS.

Pick up/move/drop via space and arrow keys, announced through a live region — pointer drag was deliberately left out: masonry's skyline packing makes a live pointer-drag reflow inherently unpredictable (which neighbor ends up where depends on the exact path the cursor took, not just where it landed) in a way a discrete keyboard step isn't.
createMasonryEngine() measures your cards via ResizeObserver and packs them with skyline packing — no manual column math, no CSS columns guesswork.
import { createMasonryEngine } from '@macrulez/masonry-kit-core'
const container = document.querySelector('#grid') as HTMLElement
const engine = createMasonryEngine(container, { columns: 'auto', minLaneSize: 240 })
engine.setItems([
{ id: 'a', el: document.querySelector('#card-a') as HTMLElement },
{ id: 'b', el: document.querySelector('#card-b') as HTMLElement },
]) A wider colSpan card takes part in the exact same skyline pack as the regular ones — no separate CSS Grid template to keep in sync with the data.
engine.setItems([
{ id: 'hero', el: heroEl, colSpan: 2 },
{ id: 'a', el: aEl },
{ id: 'b', el: bEl },
])