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.
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:
| Prop | What it does | Guide |
|---|---|---|
gridConfig | Columns, row height, margins, padding, maxRows. | Concepts |
dragConfig | Handle, cancel, threshold, bounds, snap. | Dragging |
resizeConfig | Which handles to show. | Resizing |
compactor | Packing algorithm. | Compaction |
dropConfig / onDrop | Accept external draggables. | External drop |
isDraggable / isResizable / autoSize | Grid-level toggles. | — |
onDragStart … onResizeStop | Lifecycle 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.