Cross-grid dragging
Put two or more grids on one dnd-kit provider and tiles can be dragged between them. The
source grid loses the tile; the destination gains it. Each grid keeps its own controlled layout.
<SnapGridGroup> is that shared provider — it’s just dnd-kit’s DragDropProvider, named for intent
— so each grid hosts itself with createGridContainer, and a dragged tile floats itself across the
seam (no overlay to wire).
Cross-grid is the managed, grid-to-grid case. To drag between a grid and a non-grid dnd-kit sortable (a list, a kanban column), see dnd-kit interop.
<!-- Grid.svelte — each grid hosts itself and returns its own `group`; the shared
provider comes from the enclosing SnapGridGroup. -->
<script lang="ts">
import { type Layout, createGridContainer } from "@snapgridjs/svelte";
import Tile from "./Tile.svelte";
let {
layout,
width,
onLayoutChange,
}: {
layout: Layout;
width: number;
onLayoutChange: (next: Layout) => void;
} = $props();
const container = createGridContainer(() => ({ layout, width, onLayoutChange, gridConfig: cfg }));
</script>
<div {@attach container.attach} style={container.style}>
{#each layout as it (it.i)}
<Tile id={it.i} group={container.group} />
{/each}
</div><!-- Tile.svelte -->
<script lang="ts">
import { createGridItem } from "@snapgridjs/svelte";
let { id, group }: { id: string; group: string } = $props();
// svelte-ignore state_referenced_locally
const tile = createGridItem({ id, group });
</script>
<div {@attach tile.attach} style={tile.style} class="tile">
{id}
</div><!-- Board.svelte — two grids under one SnapGridGroup exchange tiles. -->
<script lang="ts">
import { SnapGridGroup, type Layout } from "@snapgridjs/svelte";
import Grid from "./Grid.svelte";
let left = $state<Layout>(/* … */);
let right = $state<Layout>(/* … */);
const w = 480;
</script>
<SnapGridGroup>
<Grid layout={left} width={w} onLayoutChange={(next) => (left = next)} />
<Grid layout={right} width={w} onLayoutChange={(next) => (right = next)} />
</SnapGridGroup>How it works
Cross-grid dragging is just two grids on one dnd-kit DragDropProvider. <SnapGridGroup>
supplies that shared provider; a drag that starts in one grid is received by whichever grid
dnd-kit’s collision system resolves under the pointer. On drop:
- the source grid removes the tile and fires its
onLayoutChangewith the tile gone; - the destination grid inserts the tile at the drop cell and fires its
onLayoutChangewith the tile added.
Both updates happen in the same gesture, each against its own layout state.
Item ids must be unique across every grid in a group. They share one drag manager. If two
grids can hold an item with the same i, a cross-grid drop can’t tell them apart.
<SnapGridGroup> is exactly dnd-kit’s <DragDropProvider> — use whichever you like; grids under
one provider exchange tiles either way. Prefer the turnkey shell? Swap each <Grid> for a
<GridLayout> with an item snippet inside the same group;
it bundles the container and items for you.
No overlay to wire. A dragged tile floats itself — it lifts into the top layer and crosses the gap between grids unclipped, carrying its own content with it. So the preview always shows the right tile, from whichever grid it came, with nothing extra to render.
Callbacks
The destination grid signals a received tile through onLayoutChange only. It does not fire
onDragStop, because it never fired onDragStart (the source grid owns the drag’s start/stop
pair). Wire cross-grid persistence to onLayoutChange on both grids.
Dropping outside any grid
If a tile is dropped outside every grid in the group, the move reverts. The source keeps it and no
onLayoutChange fires.
Want a grid inside a tile of another rather than side-by-side siblings? See Nested grids — they also cross levels by default, and you can give a sub-grid its own provider to keep it self-contained.