Skip to Content
snapgrid is a react-grid-layout v2 alternative built on dnd-kit. Drag, resize, repack, and drag between grids.
DocumentationGuidesCompaction & packing

Compaction & packing

After every move, resize, insert, or remove, the layout is re-packed by a Compactor. Pass one as the compactor option to useGridContainer (or, identically, as a prop on <GridLayout>); swap it any time. It’s just a value.

import { useGridContainer, verticalCompactor, horizontalCompactor, noCompactor } from "@snapgridjs/react"; const { containerProps, group } = useGridContainer({ compactor: horizontalCompactor, /* … */ });
Compaction
swap the packing algorithm, then drag

Built-in compactors

Exported from @snapgridjs/react (re-exported from the core engine):

CompactorBehaviour
verticalCompactorItems fall upward to fill gaps. The default, matches react-grid-layout.
horizontalCompactorItems pack to the left.
noCompactorFree positioning: items stay where dropped and may overlap.

These honor static items and per-item collision rules.

Extra packers

@snapgridjs/extras adds variable-height packing styles. They repack the movable items by reading order — the dropped cell influences order while the algorithm decides final positions — and reserve any static tiles in place, packing the rest around them.

import { masonryCompactor, gravityCompactor, shelfCompactor } from "@snapgridjs/extras";
CompactorBehaviour
masonryCompactorMinimizes height by dropping each item into its shortest column span.
gravityCompactorTop-left gravity: each item falls into the earliest free hole (row-major).
shelfCompactorPacks left-to-right into rows, wrapping to a new shelf when full.
wrapCompactorreact-grid-layout’s wrap packer — fills a row, then wraps to the next.

The extra packers honor static tiles (reserved in place, others pack around them) but do not enforce maxRows — the Compactor.compact(layout, cols) contract only receives the column count. Use a built-in compactor when you need a hard row cap.

Fast compactors

@snapgridjs/extras also re-exports react-grid-layout’s O(n log n) vertical and horizontal compactors: drop-in replacements for the built-in cascades that produce the same packing on clean (non-overlapping) layouts but pack far faster at scale.

import { fastVerticalCompactor, fastHorizontalCompactor } from "@snapgridjs/extras"; const { containerProps, group } = useGridContainer({ compactor: fastVerticalCompactor, /* … */ });

They only help with bulk compaction: building a large layout from scratch, a responsive reflow, or compacting imported data. Dragging is already cheap (each move re-packs an already-tidy layout), so the fast variants don’t change how a drag feels; reach for them when you pack hundreds to thousands of items at once.

Each also has an …OverlapCompactor variant (fastVerticalOverlapCompactor, fastHorizontalOverlapCompactor, wrapOverlapCompactor) that permits overlapping items rather than cascading them apart.

react-grid-layout’s published benchmarks, the standard O(n²) cascade vs. the fast O(n log n) “rising tide”:

ItemsStandard verticalFast verticalSpeedup
50112 µs19 µs
100203 µs36 µs
200821 µs51 µs16×
5005.7 ms129 µs45×
ItemsStandard horizontalFast horizontalSpeedup
50164 µs12 µs14×
100477 µs25 µs19×
2001.1 ms42 µs26×

The fast compactors are the “rising tide” algorithm by @morris  (react-grid-layout #2152 ). Keep the built-in verticalCompactor / horizontalCompactor as your default. They’re the exact-parity path that honors static and maxRows; the fast variants are an opt-in for very large grids.

Writing a custom compactor

A Compactor is a small object. Implement compact(layout, cols) and return the packed layout:

import type { Compactor, Layout } from "@snapgridjs/react"; const diagonalCompactor: Compactor = { type: null, // null = free-positioning from the engine's perspective allowOverlap: false, compact(layout: Layout, cols: number): Layout { return [...layout] .sort((a, b) => a.y - b.y || a.x - b.x) .map((item, idx) => ({ ...item, x: Math.min(idx, cols - item.w), y: idx })); }, };
FieldDescription
type"vertical" | "horizontal" | "wrap" | null. Built-in types use the engine’s native cascade; null means your compact() does the work.
compact(layout, cols)Returns the re-packed layout. Called after every interaction.
allowOverlapIf true, items may overlap and the result of the move is returned as-is.
preventCollisionIf true, moves/resizes into occupied cells are rejected instead of cascading.
Last updated on