Skip to content

MasonryGrid Component ​

Renders one measured wrapper <div> per item around that item's #item slot — item content stays entirely yours, this only measures and positions the wrapper. With options.virtualize on, only the currently visible items (± overscan) actually get a wrapper/slot at all — see Virtualization.

vue
<MasonryGrid :items="items" :options="{ columns: 'auto', minLaneSize: 240 }">
  <template #item="{ item }">
    <MyCard :data="item" />
  </template>
</MasonryGrid>

Props ​

items ​

MasonryGridItem[] · required

Same shape as core's MasonryItemDescriptor (see Items & Bento Spans), minus el — the component creates and owns each item's wrapper element itself. Every field is optional, including id: id, colSpan, rowSpan, aspectRatio, estimatedSize, order.

If id is omitted, the component generates a stable one keyed by the item object's own reference (a WeakMap) — the same object in the array keeps the same id across any re-render. If items gets rebuilt into fresh objects on every render instead (e.g. .map()-ing over source data each time), each fresh object gets a newly generated id — give those an explicit id from your own data instead.

options ​

MasonryOptions · default: {}

Passed straight through to createMasonryEngine() — see Engine API. Core has no live updateOptions(), so a changed options content (compared structurally via optionsEqual, not just by reference) tears down and recreates the engine — an inline object literal that happens to have identical content on every render doesn't trigger this.

sortable ​

boolean · default: false

Turns every item wrapper into a keyboard-reorderable item — see Sortable below.

Emits ​

layout ​

Payload: MasonryItemLayout[]

Fired on every engine relayout, with every item's resolved box.

reorder ​

Payload: MasonryGridItem[]

Fired when the user reorders an item via keyboard (sortable only) — see Sortable below. Reordering never mutates items itself.

Slots ​

item ​

Scope: { item: MasonryGridItem }

The one slot rendered once per entry in items (or once per currently-visible entry, under virtualize) — the item's own content, wrapped in a measured <div class="mk-item"> the engine positions.

Sortable ​

vue
<script setup lang="ts">
import { ref } from 'vue'
import { MasonryGrid } from '@macrulez/masonry-kit-vue'

const items = ref([{ id: 'a' }, { id: 'b' }, { id: 'c' }])
</script>

<template>
  <MasonryGrid :items="items" sortable @reorder="items = $event">
    <template #item="{ item }">
      <MyCard :data="item" />
    </template>
  </MasonryGrid>
</template>

With sortable on, every item wrapper becomes keyboard-operable:

  • Space or Enter picks up the focused item (calls engine.setDragging(id) — see Engine API — so it's excluded from normal packing while active) or, if it's already picked up, drops it.
  • Arrow keys (Down/Up at direction: 'vertical', Right/Left at 'horizontal') move the picked-up item one position forward or backward in items, emitting reorder with the new array on every step.
  • Escape cancels — restores items to the array captured at pickup time (by emitting reorder with that snapshot) and releases the drag.

<MasonryGrid> never mutates items on its own — every move is a reorder emit with a brand-new array; your own v-model-style binding (@reorder="items = $event") is what actually applies it, the same way any other controlled-list pattern works.

Each wrapper gets role="button", aria-roledescription="Reorderable item", aria-pressed (reflecting pickup state), and aria-describedby pointing at a visually-hidden instructions string ("Press space or enter to pick up this item…"). Every pickup/move/drop/cancel is also announced through a visually-hidden aria-live="polite" region.

Pointer/touch drag is deliberately not implemented — see Overview for why a live pointer-drag reflow doesn't fit skyline packing the way a discrete keyboard step does.