External drop
A grid can accept non-grid dnd-kit draggables — a widget palette, a sidebar of blocks — and
turn a drop into a new layout item. Enable it with dropConfig and handle the result with
onDrop.
Set it up
The grid and the external draggable must share one dnd-kit provider, so wrap both in a
<SnapGridGroup> (dnd-kit’s DragDropProvider). The grid hosts itself with useGridContainer,
opting in via dropConfig. The draggable is a plain useDraggable from @dnd-kit/react that
carries a snapGridDrop payload describing the item to create.
import { Feedback } from "@dnd-kit/dom";
import { useDraggable } from "@dnd-kit/react";
import { SnapGridGroup, useGridContainer, useGridItem } from "@snapgridjs/react";
const CLONE = [Feedback.configure({ feedback: "clone" })];
function PaletteItem({ id, w, h }: { id: string; w: number; h: number }) {
const { ref } = useDraggable({ id, data: { snapGridDrop: { w, h } }, plugins: CLONE });
return <button ref={ref}>{`${w}×${h}`}</button>;
}
function DropGrid({ layout, width, onLayoutChange }) {
const { containerProps, group } = useGridContainer({
layout,
width,
onLayoutChange,
onDrop: (next) => onLayoutChange(next),
dropConfig: { enabled: true, defaultItem: { w: 2, h: 2 } },
});
return (
<div {...containerProps}>
{layout.map((it) => (
<Tile key={it.i} id={it.i} group={group} />
))}
</div>
);
}
function Tile({ id, group }) {
const { ref, style } = useGridItem({ id, group });
return (
<div ref={ref} style={style} className="tile">
{id}
</div>
);
}
function Board() {
const [layout, setLayout] = useState<Layout>([]);
return (
<SnapGridGroup>
<PaletteItem id="pal-wide" w={4} h={1} />
<DropGrid layout={layout} width={width} onLayoutChange={setLayout} />
</SnapGridGroup>
);
}The drag preview
Each draggable is its own floating preview — there’s no shared overlay to wire up:
- A grid tile being relocated (within or across grids) floats itself.
- A palette item floats a clone of itself — that’s the
CLONEplugin (Feedback.configure({ feedback: "clone" })) on itsuseDraggableabove.
Why a clone? dnd-kit’s default feedback floats the element itself — right for a grid tile, but it
would yank the palette item out of its tray. "clone" leaves the original in place and floats a copy
instead. The copy is just your palette markup; it becomes a grid tile only once onDrop fires.
The snapGridDrop payload
The external draggable carries data.snapGridDrop to control the inserted item:
interface GridDropData {
i?: string; // id for the new item; a unique one is generated if omitted
w?: number; // width in columns
h?: number; // height in rows
}If w/h are omitted, dropConfig.defaultItem is used (and finally 1×1, matching
react-grid-layout’s default).
dropConfig options
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Accept external draggables. |
defaultItem | { w, h } | { w: 1, h: 1 } | Size for a dropped item when the source omits snapGridDrop. |
accept | (source) => boolean | accept any non-grid draggable | Restrict which sources are accepted. |
onDrop, not onLayoutChange
An external drop fires onDrop(layout, item, event) — the next layout, the synthesized item,
and the source event — rather than onLayoutChange. That lets you add the item to both your layout
state and any side data keyed by id (titles, content, etc.) in one place.
Synthesized ids are prefixed with the grid’s id, so two drop-enabled grids in a group never mint the same id.
Beyond one-way drops
External drop is a one-way drop of a plain dnd-kit Draggable into a grid. For two-way
movement with a useSortable list or board — cards in, tiles back out, both reordering live — see
dnd-kit interop.