
Security News
Socket Releases Free Certified Patches for Critical vm2 Sandbox Escape
A critical vm2 sandbox escape can allow untrusted JavaScript to break isolation and execute commands on the host Node.js process.
@dnd-grid/react
Advanced tools
A drag-and-drop (DnD), resizable grid layout with responsive breakpoints for React.
npm install @dnd-grid/react
import { useState } from "react";
import { DndGrid, type Layout } from "@dnd-grid/react";
import "@dnd-grid/react/styles.css";
const layout: Layout = [
{ id: "a", x: 0, y: 0, w: 2, h: 2 },
{ id: "b", x: 2, y: 0, w: 2, h: 2 },
{ id: "c", x: 4, y: 0, w: 2, h: 2 },
];
function MyGrid() {
const [currentLayout, setLayout] = useState(layout);
return (
<DndGrid
layout={currentLayout}
cols={12}
rowHeight={30}
onLayoutChange={(newLayout) => setLayout(newLayout)}
>
<div key="a">A</div>
<div key="b">B</div>
<div key="c">C</div>
</DndGrid>
);
}
DndGrid is the recommended default. If you need a custom wrapper or want to
control item rendering, use the headless useDndGrid hook and render
GridItem manually.
Docs: https://dnd-grid.com/docs/hooks/use-dnd-grid
DndGrid and ResponsiveDndGrid measure container width with
ResizeObserver. Use containerProps to style the measurement wrapper, and
measureBeforeMount / initialWidth to control the first render.
import { DndGrid, type Layout } from "@dnd-grid/react";
const layout: Layout = [
{ id: "a", x: 0, y: 0, w: 2, h: 2 },
{ id: "b", x: 2, y: 0, w: 2, h: 2 },
];
function MyGrid() {
return (
<DndGrid
layout={layout}
cols={12}
rowHeight={30}
containerProps={{ className: "w-full", style: { maxWidth: 600 } }}
>
<div key="a">A</div>
<div key="b">B</div>
</DndGrid>
);
}
If you already measure width (SSR or custom measurement), pass the width directly:
import {
DndGrid,
type Layout,
useContainerWidth,
} from "@dnd-grid/react";
const layout: Layout = [
{ id: "a", x: 0, y: 0, w: 2, h: 2 },
{ id: "b", x: 2, y: 0, w: 2, h: 2 },
];
function MyGrid() {
const { width, containerRef, mounted } = useContainerWidth({
measureBeforeMount: true,
initialWidth: 600,
});
return (
<div ref={containerRef} className="w-full" style={{ maxWidth: 600 }}>
{mounted && width > 0 && (
<DndGrid
layout={layout}
cols={12}
rowHeight={30}
width={width}
>
<div key="a">A</div>
<div key="b">B</div>
</DndGrid>
)}
</div>
);
}
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | required | Grid items; each child key must match a layout id. |
layout | Layout | [] | Array of layout items; missing entries are derived from children (dev warning). |
width | number | undefined | Optional container width in pixels. When provided, measurement is skipped. |
measureBeforeMount | boolean | true | Delay rendering until the container width is measured. |
initialWidth | number | 1280 | Width used before measurement when rendering early. |
containerProps | HTMLAttributes<HTMLDivElement> | undefined | Props applied to the measurement wrapper. |
cols | number | 12 | Number of columns |
rowHeight | number | 150 | Height of a row in pixels |
autoSize | boolean | true | Auto-size the container height |
maxRows | number | Infinity | Maximum number of rows |
gap | number | { top: number; right: number; bottom: number; left: number } | 10 | Gap around items |
containerPadding | number | { top: number; right: number; bottom: number; left: number } | null | null | Container padding (null uses gap) |
className | string | "" | Extra class for the grid container |
style | CSSProperties | {} | Inline styles for the grid container |
draggable | boolean | true | Enable dragging |
resizable | boolean | true | Enable resizing |
autoScroll | boolean | AutoScrollOptions | true | Auto-scroll when dragging near scroll edges |
bounded | boolean | false | Keep items within container bounds |
compactor | Compactor | verticalCompactor | Compaction strategy |
constraints | LayoutConstraint[] | defaultConstraints | Constraints applied during drag/resize |
validation | boolean | true in dev, false in prod | Validate layouts at runtime with Zod |
callbackThrottle | number | { drag?: number; resize?: number } | undefined | Throttle onDrag and onResize callbacks |
animationConfig | AnimationConfig | default config | Configure spring and shadow animations |
reducedMotion | ReducedMotionSetting | boolean | "system" | Motion preference override |
resizeHandles | ResizeHandleAxis[] | ["se"] | Resize handle positions |
resizeHandle | ReactElement | ((axis, ref) => ReactElement) | undefined | Custom resize handle component |
transformScale | number | 1 | Scale factor for CSS transforms |
dragTouchDelayDuration | number | 250 | Touch delay before drag starts (ms) |
dragHandle | string | "" | Selector for drag handles |
dragCancel | string | "" | Selector for elements that cancel drag |
droppingItem | Partial<LayoutItem> | { id: "__dropping-elem__", w: 1, h: 1 } | Defaults for the dropping placeholder |
slotProps | SlotProps | undefined | Slot styling for items, placeholders, and handles |
liveAnnouncements | LiveAnnouncementsOptions | false | enabled | Configure aria-live announcements or disable |
innerRef | Ref<HTMLDivElement> | undefined | Ref to the container element |
aria-label | string | undefined | Accessible label for the grid |
aria-labelledby | string | undefined | ID(s) of labelling elements |
aria-describedby | string | undefined | ID(s) of descriptive elements |
Layout is provided via the layout prop; data-grid on children is not supported.
Missing layout entries are derived from children (with a dev warning).
Drop behavior is enabled when you provide onDrop or onDropDragOver.
DndGrid renders role="grid" and assigns role="gridcell" plus row/column indices to items. Placeholders and static items are excluded from set indexing.
Use ResponsiveDndGrid for breakpoint-aware layouts:
import { ResponsiveDndGrid, type ResponsiveLayouts } from "@dnd-grid/react";
const layouts: ResponsiveLayouts = {
lg: [{ id: "a", x: 0, y: 0, w: 3, h: 2 }],
md: [{ id: "a", x: 0, y: 0, w: 4, h: 2 }],
};
function ResponsiveGrid() {
return (
<ResponsiveDndGrid
layouts={layouts}
gap={{ lg: 16, md: { top: 12, right: 16, bottom: 12, left: 16 } }}
containerPadding={16}
>
<div key="a">A</div>
<div key="b">B</div>
</ResponsiveDndGrid>
);
}
For custom wrappers or manual width control, compose
useDndGridResponsiveLayout with DndGrid.
Each item in the layout array has these properties:
interface LayoutItem {
id: string; // Unique identifier
x: number; // X position in grid units
y: number; // Y position in grid units
w: number; // Width in grid units
h: number; // Height in grid units
minW?: number; // Minimum width
maxW?: number; // Maximum width
minH?: number; // Minimum height
maxH?: number; // Maximum height
data?: TData; // Optional metadata
resizeHandles?: ResizeHandleAxis[]; // Override resize handle positions
constraints?: LayoutConstraint[]; // Per-item constraints
static?: boolean; // Cannot be moved or resized
draggable?: boolean; // Override grid draggable
resizable?: boolean; // Override grid resizable
bounded?: boolean; // Override grid bounded
}
The default stylesheet ships as layout + theme layers. Import the combined file, or split them as needed:
import "@dnd-grid/react/styles.css";
import "@dnd-grid/react/base.css";
import "@dnd-grid/react/theme.css";
Theme variables are exposed as CSS custom properties:
:root {
--dnd-grid-scale: 1;
--dnd-grid-radius: 24px;
--dnd-grid-placeholder-bg: rgba(0, 0, 0, 0.012);
--dnd-grid-placeholder-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.06);
--dnd-grid-handle-size: 16px;
--dnd-grid-handle-bg: rgb(255, 255, 255);
--dnd-grid-handle-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.06),
0 9px 9px rgba(0, 0, 0, 0.04),
0 5.83333px 5.27083px rgba(0, 0, 0, 0.03),
0 3.46667px 2.86667px rgba(0, 0, 0, 0.024),
0 1.8px 1.4625px rgba(0, 0, 0, 0.02),
0 0.733333px 0.733333px rgba(0, 0, 0, 0.016);
--dnd-grid-transition-duration: 200ms;
--dnd-grid-transition-easing: cubic-bezier(0.2, 0, 0, 1);
}
Compactors control how items are packed after drag/resize. You can use the built-ins or provide your own:
import {
verticalCompactor,
horizontalCompactor,
noCompactor,
fastVerticalCompactor,
} from "@dnd-grid/react";
<DndGrid compactor={verticalCompactor} />;
<DndGrid compactor={horizontalCompactor} />;
<DndGrid compactor={noCompactor} />;
<DndGrid compactor={fastVerticalCompactor} />;
Constraints let you enforce rules during drag/resize:
import { defaultConstraints, aspectRatio, gridBounds } from "@dnd-grid/react";
<DndGrid constraints={[gridBounds, aspectRatio(16 / 9)]} />;
Read the layout item and interaction state from within a child component:
import { useDndGridItemState } from "@dnd-grid/react";
function Card() {
const { item, state } = useDndGridItemState();
return (
<div className={state.resizing ? "is-resizing" : ""}>
Item {item.id}
</div>
);
}
The hook must be used inside a DndGrid item (it throws if rendered elsewhere).
If you manage drag state outside of the grid (for example with @dnd-kit), use
the grid handle to drive the dropping placeholder and commit the drop:
const gridRef = useRef<DndGridHandle>(null);
const handleMove = (clientX: number, clientY: number) => {
gridRef.current?.handleExternalDrag({ clientX, clientY });
};
const handleDrop = (clientX: number, clientY: number, event?: Event) => {
gridRef.current?.handleExternalDrag({
clientX,
clientY,
event,
type: "drop",
});
};
const handleCancel = (clientX: number, clientY: number, event?: Event) => {
gridRef.current?.handleExternalDrag({
clientX,
clientY,
event,
type: "cancel",
});
};
Use onDropDragOver to override the dropping placeholder size while dragging.
If your drag library exposes a native event, pass it via event instead of
generating one.
| Callback | Type | Description |
|---|---|---|
onLayoutChange | (layout: Layout) => void | Called when layout changes |
onDragStart | (event: GridDragEvent) => void | Called when drag starts |
onDrag | (event: GridDragEvent) => void | Called during drag |
onDragEnd | (event: GridDragEvent) => void | Called when drag ends |
onResizeStart | (event: GridResizeEvent) => void | Called when resize starts |
onResize | (event: GridResizeEvent) => void | Called during resize |
onResizeEnd | (event: GridResizeEvent) => void | Called when resize ends |
onDrop | (layout, item, e) => void | Called when item is dropped |
onDropDragOver | (e) => { w?: number; h?: number } | false | Customise dropping item |
Common CSS classes include:
.dnd-grid - Grid container.dnd-grid-item - Grid item.dnd-grid-item-content - Non-placeholder grid item.dnd-grid-placeholder - Placeholder during drag/resize.dnd-draggable - Draggable item.dnd-draggable-dragging - Item being dragged.resizing - Item currently being resized.static - Static (locked) item.dnd-grid-animating - Item settling after drag.dropping - Dropping placeholder item.dnd-grid-resize-handle - Resize handle elementbody.dnd-grid-dragging - Global dragging statebody.dnd-grid-resizing - Global resizing stateThe grid sets data-* attributes for stateful styling:
data-dnd-grid on the containerdata-dnd-grid-live-region on the aria-live elementdata-dnd-grid-item on each itemdata-dnd-grid-item-id on each itemdata-dnd-grid-handle and data-handle-axis on resize handlesdata-dragging, data-resizing, data-settling, data-disabled, data-draggable, data-resizable on items when true[data-dnd-grid-item][data-resizing] {
z-index: 10;
}
[data-dnd-grid-handle][data-handle-axis="sw"] {
cursor: sw-resize;
}
This project is based on react-grid-layout by Samuel Reed (STRML). The original project provided the foundation for the grid layout algorithms and core functionality.
MIT Licence - Copyright (c) 2025 Matthew Blode
FAQs
A drag-and-drop (DnD), resizable grid layout for React
The npm package @dnd-grid/react receives a total of 681 weekly downloads. As such, @dnd-grid/react popularity was classified as not popular.
We found that @dnd-grid/react demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
A critical vm2 sandbox escape can allow untrusted JavaScript to break isolation and execute commands on the host Node.js process.

Research
Five malicious NuGet packages impersonate Chinese .NET libraries to deploy a stealer targeting browser credentials, crypto wallets, SSH keys, and local files.

Security News
pnpm 11 turns on a 1-day Minimum Release Age and blocks exotic subdeps by default, adding safeguards against fast-moving supply chain attacks.