Skip to content

Engine API ​

createMasonryEngine(container, options?) — the framework-agnostic engine every other part of the package is built on. You own the item elements and their content; this measures them and packs them into lanes.

ts
function createMasonryEngine(container: HTMLElement, options?: MasonryOptions): MasonryEngine

Sets container's CSS position to relative itself — no manual CSS needed. At direction: 'vertical', it also sets overflow-x: clip on the container: an item's width updates instantly on a lane-count change from a resize, but its position only FLIP-animates over 250ms, so it can briefly extend past the right edge mid-transition — clip keeps that from turning into a page-level horizontal scrollbar. At direction: 'horizontal' it sets overflow-x: auto; overflow-y: hidden instead — the container becomes the horizontal scroll viewport itself (more on this below). This page covers the engine's core packing options, its methods, and its one event. Options specific to virtualization, animation, or the SSR fallback live on their own pages — see Virtualization, Animations, and SSR Rendering.

Options ​

direction ​

'vertical' | 'horizontal' · default: 'vertical'

'vertical' packs into columns (the main axis grows downward); 'horizontal' packs into rows (the main axis grows rightward, and the container scrolls horizontally — see below).

columns ​

LaneSpec · default: 'auto'

Lane count at direction: 'vertical'. 'auto' fits as many lanes of at least minLaneSize as the container's width allows; a plain number fixes the count; a breakpoints object ({ default: 2, 768: 3, 1200: 4 }) picks the value at the smallest key >= the container's current width, or default if none matches.

rows ​

LaneSpec · default: 'auto'

Same shape as columns, used at direction: 'horizontal' instead.

minLaneSize ​

number · default: 240

Required for columns/rows: 'auto' — the fluid lane size that decides how many lanes fit.

gap ​

number | { main?: number; cross?: number } · default: 16 for both

A single number applies to both axes. main is the gap along the packing/growth axis (vertical space between stacked items in a column); cross is the gap between lanes themselves.

placement ​

'balanced' | 'ordered' · default: 'balanced'

'balanced' places each item in whichever lane (or lane group, for a spanning item) has the least content so far — classic skyline packing. 'ordered' is strict round-robin instead: less visually balanced, but keeps physical placement order equal to item order.

Example — every packing option at once:

ts
const engine = createMasonryEngine(gridEl, {
  direction: 'vertical', // columns; 'horizontal' would pack into rows instead
  columns: { default: 2, 768: 3, 1200: 4 }, // 2 lanes narrower than 768px, 3 up to 1200px, 4 beyond
  minLaneSize: 240, // only consulted if columns were 'auto' instead — has no effect with a fixed/breakpoints spec
  gap: { main: 20, cross: 16 }, // 20px between stacked items, 16px between lanes
  placement: 'balanced', // each item goes to the least-filled lane(s), not strict round-robin
})

Methods ​

setItems(items: MasonryItemDescriptor[]): void ​

Replaces the full set of items the engine measures and packs. See Items & Bento Spans for MasonryItemDescriptor's shape. Triggers an immediate relayout.

updateItem(id: string, patch: Partial<Omit<MasonryItemDescriptor, 'id'>>): void ​

Merges patch into the item with the given id — e.g. to change its colSpan without re-passing every other item. No-op if id isn't currently registered.

addItem(item: MasonryItemDescriptor, index?: number): void ​

Adds a single item without replacing the others, at index (end of the list if omitted).

removeItem(id: string): void ​

Removes a single item by id.

relayout(): void ​

Forces an immediate recomputation, bypassing the engine's requestAnimationFrame batching — useful right after a manual content mutation the engine wouldn't otherwise notice (its own ResizeObserver covers element size changes automatically; this is for everything else).

getLayout(): MasonryItemLayout[] ​

The most recent resolved layout — one { id, x, y, width, height } box per packed item, already mapped to physical coordinates regardless of direction.

getVisibleIds(): string[] ​

Ids that should have a real DOM element right now. Every packed item's id when virtualize is off — see Virtualization for what it means when virtualization is on.

setDragging(id: string | null): void ​

Excludes id from packing/positioning entirely — everyone else reflows immediately as if it weren't in the list, and its element's transform/opacity/classes are left 100% to the caller until this is called again with null, which re-includes it on the next relayout. Since its transform is whatever the caller last set (not empty), it FLIP-animates from there into its packed slot instead of popping in. This is the primitive the Vue/React adapters' sortable keyboard reordering is built on — see MasonryGrid Component.

on(event, handler): () => void ​

Subscribes to an engine event (see Events below). Returns an unsubscribe function.

destroy(): void ​

Removes all listeners (resize/scroll), clears every item, and tears down the horizontal-mode scroll spacer if one was created. Call this when the container is about to leave the DOM — <MasonryGrid> and useMasonry() both already do this for you on unmount.

Events ​

Subscribe via engine.on(eventName, handler).

layout ​

{ items: MasonryItemLayout[]; visibleIds: string[] }

Fired at the end of every relayout (initial, structural change, resize-, or scroll-triggered) with every item's resolved box. visibleIds is the same set getVisibleIds() returns for this pass.