Skip to content

Masonry Kit

v0.1.4UI ComponentsVanilla JSVueNuxtReact

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.

Masonry Kit
Get started →
npm install @macrulez/masonry-kit-core
01 — Purpose

When you'd reach for this

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.

A Pinterest-style card gallery

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.

A bento dashboard with differently sized cards

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.

A feed with hundreds or thousands of cards

Virtualization keeps relayout cheap by estimating off-screen items instead of measuring the entire list on every scroll.

A masonry grid that doesn't flash on page load

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.

02 — Features

At a glance

Skyline packing generalized to spans

Skyline packing generalized to spans

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.

One engine for both directions

One engine for both directions

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

Transform-based positioning, not CSS columns/Grid

Transform-based positioning, not CSS columns/Grid

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.

Virtualization for large lists

Virtualization for large lists

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.

FLIP-animated reflow

FLIP-animated reflow

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

Keyboard-reorderable items

Keyboard-reorderable items

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.

03 — Quick example

See how it works

Cards of different heights, packed automatically

createMasonryEngine() measures your cards via ResizeObserver and packs them with skyline packing — no manual column math, no CSS columns guesswork.

basic.ts
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 },
])

Bento cards, same packing

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.

bento.ts
engine.setItems([
  { id: 'hero', el: heroEl, colSpan: 2 },
  { id: 'a', el: aEl },
  { id: 'b', el: bEl },
])