Skip to Content
New — snapgrid now ships for Svelte 5: the same dnd-kit-native, headless-first grid — drag, resize, repack, and drag between grids. See it in Svelte →
VueDocumentationGuidesComponent layer

Component layer

snapgrid is headless-first, but you don’t have to wire the composables yourself. The component layer is a thin shell in the react-grid-layout style over those exact composables: <GridLayout> bundles the dnd-kit DragDropProvider, the container, the items, and the placeholder, so you never touch dnd-kit directly.

This is the escape hatch. If the headless provider/host/tile wiring is more than you want, reach for <GridLayout> — it mirrors react-grid-layout v2, so it’s the gentlest on-ramp. When one grid later needs custom markup, drop just that grid down to the composables. Same engine; the two layers mix freely.

<GridLayout>

A controlled grid. Each layout item’s content comes from an item scoped slot — Vue has no keyed-children analog, so the slot receives the item (keyed by i) and snapgrid positions it and renders the resize handles and placeholder for you.

<script setup> import { ref } from "vue"; import { GridLayout, useContainerWidth } from "@snapgridjs/vue"; const { width, setRef } = useContainerWidth(); const layout = ref([ { i: "a", x: 0, y: 0, w: 4, h: 2 }, { i: "b", x: 4, y: 0, w: 4, h: 2 }, { i: "c", x: 8, y: 0, w: 4, h: 3 }, ]); </script> <template> <div :ref="setRef"> <GridLayout :layout="layout" :width="width" :on-layout-change="(next) => (layout = next)" :grid-config="{ cols: 12, rowHeight: 60, margin: [12, 12] }" :resize-config="{ handles: ['se', 'e', 's'] }" > <template #item="{ item }"> <div class="tile">{{ item.i }}</div> </template> </GridLayout> </div> </template>

It supplies its own DragDropProvider; nest several (or wrap them in <SnapGridGroup>) and they share one.

<GridLayout> is also the way around Vue’s inject rule: it renders the provider and its grid surface as a separate child component, so unlike a bare useGridContainer call it’s safe to use in the very component that hosts the grid. See Architecture for why.

Component layer
the turnkey <GridLayout> — no composables, no dnd-kit wiring

Config props map 1:1 to the composable options

Every behaviour prop on <GridLayout> is the same option you’d pass to useGridContainer — only the spelling differs (a component prop vs. an object key). So each feature guide applies to both layers:

PropWhat it doesGuide
gridConfigColumns, row height, margins, padding, maxRows.Concepts
dragConfigHandle, cancel, threshold, bounds, snap.Dragging
resizeConfigWhich handles to show.Resizing
compactorPacking algorithm.Compaction
dropConfig / onDropAccept external draggables.External drop
isDraggable / isResizable / autoSizeGrid-level toggles.
onDragStartonResizeStopLifecycle callbacks.Events

See Components → GridLayout for the complete prop table.

<ResponsiveGridLayout>

The turnkey wrapper over useResponsiveLayout. Give it a per-breakpoint layouts map instead of a single layout; it switches column count and layout as width crosses breakpoints.

<script setup> import { ref } from "vue"; import { ResponsiveGridLayout, useContainerWidth } from "@snapgridjs/vue"; const { width, setRef } = useContainerWidth(); const layouts = ref({ lg: [/* … */] }); </script> <template> <div :ref="setRef"> <ResponsiveGridLayout :width="width" :layouts="layouts" :on-layout-change="(_active, all) => (layouts = all)" > <template #item="{ item }"> <div class="tile">{{ item.i }}</div> </template> </ResponsiveGridLayout> </div> </template>

Cross-grid with components

Wrap sibling grids in <SnapGridGroup> — the shared provider — and tiles drag between them. (Nested <GridLayout>s share a provider automatically.)

<template> <SnapGridGroup> <GridLayout :layout="left" :width="w" :on-layout-change="(next) => (left = next)"> <template #item="{ item }"> <div class="tile">{{ item.i }}</div> </template> </GridLayout> <GridLayout :layout="right" :width="w" :on-layout-change="(next) => (right = next)"> <template #item="{ item }"> <div class="tile">{{ item.i }}</div> </template> </GridLayout> </SnapGridGroup> </template>

Item ids must be unique across all grids that share a provider — they share one drag manager.

Composing the pieces

The layer isn’t all-or-nothing. <GridItem> and <GridPlaceholder> are the same tiles <GridLayout> renders, exposed so you can drop them into a surface you host with useGridContainer — handy when you want the styled tile but control of the container:

<!-- Surface.vue — rendered inside a <DragDropProvider> or <SnapGridGroup>. --> <script setup> import { GridItem, GridPlaceholder, useGridContainer } from "@snapgridjs/vue"; const props = defineProps({ layout: Array, width: Number, onLayoutChange: Function }); // Destructure so the template auto-unwraps each ref. const { setRef, style, group } = useGridContainer(() => ({ layout: props.layout, width: props.width, onLayoutChange: props.onLayoutChange, })); </script> <template> <div :ref="setRef" :style="style"> <GridItem v-for="it in layout" :key="it.i" :id="it.i" :group="group">{{ it.i }}</GridItem> <GridPlaceholder :group="group" /> </div> </template>

<GridItem> applies the stable .snapgrid-item class and data-grid-id / data-dragging attributes (see Styling) and updates only its own slice, so re-rendering the surface doesn’t touch every tile.

There’s no drag overlay to manage at either layer — a dragged tile floats itself. See Components for the full prop tables and Headless usage for the composables.

Last updated on