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

Migrating from react-grid-layout

snapgrid deliberately keeps react-grid-layout’s mental model: a controlled array of { i, x, y, w, h } items, a cols grid, rowHeight, and onLayoutChange. Most of the migration is moving a handful of top-level props into config objects, and deleting two CSS imports.

This guide assumes you’re coming from react-grid-layout (RGL) and know its API.

What you keep, what you gain

snapgrid is a capability superset of react-grid-layout’s core, on a different, more capable engine:

snapgridreact-grid-layout
Layout model ({ i, x, y, w, h }, controlled)
Responsive breakpoints
Resize handles · per-item min/max · static tiles
Drag tiles between grids✅ built-in (SnapGridGroup)
Nested grids✅ isolated per level⚠️ manual
Keyboard dragging / a11y✅ Enter · arrows · Esc
Headless (bring your own markup)✅ provider + hooks❌ renders its own DOM
Pluggable packing✅ vertical / horizontal / none + masonry / gravity / shelf + customvertical / horizontal / none
Accept external draggablesdropConfig / onDrop⚠️ droppingItem
Stylingunstyled: ships no CSSrequires react-grid-layout.css + react-resizable.css
Interaction enginednd-kit : its latest framework-agnostic core (pointer · touch · keyboard)react-draggable + react-resizable
TypeScript types✅ bundledvia @types/react-grid-layout

react-grid-layout is mature, widely deployed, and battle-tested. This table is about capability differences, not quality. snapgrid is new; if you need a proven incumbent today, RGL is a great choice.

Bundle size

snapgrid itself is ~6 kB brotli, but it’s built on dnd-kit  (~27 kB), so a fresh install is ~33 kB brotli, roughly 2× react-grid-layout v2’s ~15 kB (minified, React excluded). That weight is dnd-kit, and it’s a deliberate trade:

  • dnd-kit is the de-facto standard for drag-and-drop in React. Its accessible, multi-sensor engine is what gives snapgrid keyboard dragging, touch support, and cross-grid out of the box (things RGL’s older react-draggable / react-resizable stack doesn’t).
  • If your app already uses dnd-kit, snapgrid adds only ~6 kB.
  • snapgrid tracks dnd-kit’s latest framework-agnostic line (@dnd-kit/react), the line dnd-kit recommends over the legacy @dnd-kit/core.

1. Swap the imports

// Before — react-grid-layout import GridLayout, { WidthProvider } from "react-grid-layout"; import "react-grid-layout/css/styles.css"; import "react-resizable/css/styles.css"; const Grid = WidthProvider(GridLayout); // After — snapgrid (ships no CSS; measure width with a hook) import { GridLayout, useContainerWidth, type Layout } from "@snapgridjs/react";

Install the package and its dnd-kit peers:

pnpm add @snapgridjs/react @dnd-kit/react @dnd-kit/dom

snapgrid ships no stylesheet. Delete the react-grid-layout/css/styles.css and react-resizable/css/styles.css imports and style .snapgrid-item, the resize handles, and the placeholder yourself. See Styling.

2. Map the props

Most RGL props that lived at the top level move into gridConfig, dragConfig, or resizeConfig.

<GridLayout>

react-grid-layoutsnapgridNotes
width / WidthProviderwidthFrom useContainerWidth().
layoutlayoutSame item shape (i, x, y, w, h, minW, maxW, minH, maxH, static).
onLayoutChangeonLayoutChangeSame (layout) => void.
colsgridConfig.colsDefault 12.
rowHeightgridConfig.rowHeightDefault 150.
margingridConfig.margin[x, y], default [10, 10].
containerPaddinggridConfig.containerPaddingDefault falls back to margin.
maxRowsgridConfig.maxRows
compactTypecompactorverticalCompactor / horizontalCompactor / noCompactor. See below.
isDraggableisDraggableOr dragConfig.enabled.
isResizableisResizable
draggableHandledragConfig.handleCSS selector.
draggableCanceldragConfig.cancelCSS selector.
isBoundeddragConfig.bounded
resizeHandlesresizeConfig.handlese.g. ["se", "e", "s"]. Default ["se"].
onDragStart · onDrag · onDragStopsame names
onResizeStart · onResize · onResizeStopsame names
isDroppable · droppingItem · onDropdropConfig + onDropSee External drop.
autoSizeautoSize
useCSSTransforms · transformScale · measureBeforeMountNot needed; snapgrid uses transforms + ResizeObserver by default.

<ResponsiveGridLayout>

These stay as top-level props, same as RGL:

react-grid-layoutsnapgrid
breakpointsbreakpoints
cols (per breakpoint)cols
layoutslayouts
onBreakpointChange(bp, cols)onBreakpointChange(breakpoint, cols)
onLayoutChange(layout, layouts)onLayoutChange(layout, layouts)
rowHeight · margin · containerPaddingsame

The default breakpoints and columns match RGL’s: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 } and { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }.

compactTypecompactor

RGL compactTypesnapgrid compactor
"vertical" (default)verticalCompactor (default)
"horizontal"horizontalCompactor
nullnoCompactor

@snapgridjs/extras adds masonryCompactor, gravityCompactor, and shelfCompactor, or you can write your own. See Compaction & packing.

3. Side-by-side

// Before — react-grid-layout const Grid = WidthProvider(GridLayout); function Board() { const [layout, setLayout] = useState([ { i: "a", x: 0, y: 0, w: 4, h: 2 }, { i: "b", x: 4, y: 0, w: 4, h: 2 }, ]); return ( <Grid layout={layout} cols={12} rowHeight={30} compactType="vertical" draggableHandle=".drag-handle" onLayoutChange={setLayout} > <div key="a"><span className="drag-handle" /> A</div> <div key="b"><span className="drag-handle" /> B</div> </Grid> ); }
// After — snapgrid function Board() { const { width, containerRef } = useContainerWidth(); const [layout, setLayout] = useState<Layout>([ { i: "a", x: 0, y: 0, w: 4, h: 2 }, { i: "b", x: 4, y: 0, w: 4, h: 2 }, ]); return ( <div ref={containerRef}> <GridLayout layout={layout} width={width} onLayoutChange={setLayout} gridConfig={{ cols: 12, rowHeight: 30 }} dragConfig={{ handle: ".drag-handle" }} > <div key="a" className="tile"><span className="drag-handle" /> A</div> <div key="b" className="tile"><span className="drag-handle" /> B</div> </GridLayout> </div> ); }

(compactor={verticalCompactor} is the default, so it’s omitted above.)

4. The three behaviours that differ

These are the only things likely to surprise you.

Compaction runs during a drag, not on render

RGL re-compacts the layout on every render. snapgrid renders your layout verbatim and only re-packs during an active drag. So your initial layout must already be its resting state. If you relied on RGL to tidy a hand-written layout on mount, pre-compact it once:

import { verticalCompactor } from "@snapgridjs/react"; const initial = verticalCompactor.compact(rawLayout, 12); // cols const [layout, setLayout] = useState(initial);

A layout with gaps or overlaps will stay that way until the user drags something. Feed snapgrid a clean, non-overlapping layout (or pre-compact as above).

Always controlled

RGL can run uncontrolled, holding layout state internally if you don’t pass layout. snapgrid is always controlled: keep layout in state and apply every onLayoutChange. There is no uncontrolled mode.

No preventCollision / allowOverlap

Placement is governed by the compactor, not collision flags. For free-form placement where tiles can sit anywhere (RGL’s allowOverlap / compactType={null}), use noCompactor.

Anything missing?

If you hit an RGL feature without a clear equivalent here, open an issue . Migration gaps are high-priority.

Last updated on