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 →

Factories

snapgrid’s primary API. Each returns Svelte attachments, style strings, and reactive state to apply to your own elements. See Headless usage for the full picture.

Headless grids render inside a dnd-kit DragDropProvider that you supply. createGridContainer is the grid host and returns the grid’s group; the item-level factories take that group to resolve their grid.

These are runes-based factories: call one once, during component initialization, and keep the handle it returns. Its fields are reactive getters — read them in your template rather than destructuring. attach is a Svelte attachment  you apply with {@attach handle.attach}; style is a ready-made CSS string — bind it directly, don’t spread it.

createContainerWidth

Measures a container’s width with a ResizeObserver (SSR-safe). Replaces react-grid-layout’s WidthProvider.

<script lang="ts"> import { createContainerWidth } from "@snapgridjs/svelte"; const size = createContainerWidth({ initialWidth: 1024 }); </script> <div {@attach size.attach}> <!-- pass size.width to the grid --> </div>
TypeDescription
options.initialWidthnumberWidth before measurement. Default 1280.
widthnumberMeasured width (or initialWidth before mount).
mountedbooleantrue once measured at least once.
attach(node: HTMLElement) => () => voidAttachment for the element to measure.

createGridContainer

The grid host: takes a getter of the controlled layout + config, owns the grid’s drag/resize session, registers the droppable surface, and returns an attach + style for your container plus the grid’s group. Options are passed as a getter (() => ({ … })) so the grid tracks your live state. Call it inside a component rendered under a <DragDropProvider>.

<script lang="ts"> import { createGridContainer } from "@snapgridjs/svelte"; import Tile from "./Tile.svelte"; let { layout, width, onLayoutChange } = $props(); const container = createGridContainer(() => ({ layout, width, onLayoutChange })); </script> <div {@attach container.attach} style={container.style}> {#each layout as it (it.i)} <Tile id={it.i} group={container.group} /> {/each} </div>

Options (UseGridControllerOptions)

The controlled state plus the per-grid config. These are exactly <GridLayout>’s props minus class / style (here you own the surface element, so you style it directly).

OptionTypeDefaultDescription
layoutLayoutRequired. Controlled array of items. Never mutated.
widthnumberRequired. Container width in px (from createContainerWidth).
onLayoutChange(layout: Layout) => voidCalled with the next layout when an interaction commits. The grid is controlled — apply it, or nothing moves.
gridConfigPartial<GridConfig>see GridConfigColumns, row height, margins, padding, maxRows. Unset fields use defaults.
dragConfigDragConfigDrag behaviour: handle (CSS selector — headless prefers attachHandle), cancel, threshold, bounded, snapToGrid.
resizeConfigResizeConfig{ handles: ["se"] }Resize behaviour: which handle axes are allowed.
dropConfigDropConfig{ enabled: false }Accept external draggables: enabled, defaultItem, accept.
accept(source) => booleandnd-kit interop: also accept a foreign sortable (e.g. a tray card) as a drop target. You drive the receive yourself in onDragOver with snapMove.
compactorCompactorverticalCompactorPacking algorithm. Swap at runtime.
isDraggablebooleantrueGrid-level drag toggle (overrides per-item when false).
isResizablebooleantrueGrid-level resize toggle.
autoSizebooleantrueGrow the surface height to fit content.
idstringautoStable id for the droppable surface; surfaced back as group.
typestring"grid"The droppable surface’s dnd-kit type. Override to namespace grids for interop (branch onDragOver on it, or have a foreign draggable accept only this grid). Grids resolve by id, so any value still receives tiles + cross-grid drops.
onDragStart onDrag onDragStopGridEventCallbackDrag lifecycle (layout, oldItem, newItem, placeholder, event, node).
onResizeStart onResize onResizeStopGridEventCallbackResize lifecycle, same signature.
onDrop(layout, item, event) => voidFired when an external draggable is dropped in — instead of onLayoutChange.

Returns (GridContainerResult)

FieldTypeDescription
attach(node: HTMLElement) => () => voidAttachment for your surface element.
stylestringReactive inline-style string: position: relative + width + auto-sized height. Bind it directly.
isDropTargetbooleantrue while a compatible draggable is over the grid — handy for highlighting.
groupstringThis grid’s id (the id option, or a generated one). Pass it to every item-level factory.
controllerGridControllerThe live store, for advanced composition.

createGridItem

Wires a single tile: drag activation, positioning style, and drag state. Takes an options object, mirroring dnd-kit’s createSortable: id, group (the value its createGridContainer returned), and an optional type.

<script lang="ts"> import { createGridItem } from "@snapgridjs/svelte"; let { id, group } = $props(); // svelte-ignore state_referenced_locally const tile = createGridItem({ id, group }); </script> <div {@attach tile.attach} style={tile.style}>{tile.item?.i}</div>
OptionTypeDefaultDescription
idstringRequired. Matches the layout item’s i.
groupstringRequired. The owning grid’s id (from its createGridContainer).
typestring"grid-item"The dnd-kit sortable type the tile carries. Override to namespace tiles for interop — e.g. so a foreign sortable list accepts only one grid’s tiles. The grid identifies its own tiles by their payload, not this string, so any value still drags and crosses grids.
TypeDescription
attach(node: HTMLElement) => () => voidAttachment for the tile element.
attachHandle(node: HTMLElement) => () => voidOptional drag handle — attach to a child to restrict pointer dragging to it. The headless form of dragConfig.handle.
stylestringposition: absolute + left/top + size. Reflow animates on the compositor (a transform FLIP), so the dnd-kit float reads the tile’s true rect during ecosystem hand-offs.
isDraggingbooleanTrue while this tile is the active drag source.
itemLayoutItem | undefinedThe tile’s current (possibly reflowed) entry.

createGridResizeHandle

Models a resize handle as its own draggable. Position and style it however you like; group is the grid’s id.

<script lang="ts"> import { createGridResizeHandle } from "@snapgridjs/svelte"; let { itemId, group } = $props(); const handle = createGridResizeHandle({ id: itemId, handle: "se", group }); </script> <span {@attach handle.attach} {...handle.handleProps}></span>

handleProps carries the marker attribute that excludes the handle from item-drag activation.

createGridPlaceholder

Returns where the landing-cell marker should render via .current, or null when idle. group is the grid’s id.

<script lang="ts"> import { createGridPlaceholder } from "@snapgridjs/svelte"; let { group } = $props(); const placeholder = createGridPlaceholder(group); </script> {#if placeholder.current} <div style={placeholder.current.style}></div> {/if}

createResponsiveLayout

The headless engine behind ResponsiveGridLayout. Resolves the active breakpoint, columns, and layout from the container width. Options are passed as a getter so it tracks your live width/layouts.

<script lang="ts"> import { createResponsiveLayout, type ResponsiveLayouts } from "@snapgridjs/svelte"; let { width }: { width: number } = $props(); let layouts = $state<ResponsiveLayouts>({ lg: [/* … */] }); const responsive = createResponsiveLayout(() => ({ width, layouts, onLayoutChange: (active, all) => (layouts = all), onBreakpointChange: (bp, cols) => {}, })); </script>
OptionTypeDescription
widthnumberCurrent container width.
layoutsResponsiveLayoutsPer-breakpoint layout map.
breakpointsBreakpointsOptional; defaults to DEFAULT_BREAKPOINTS.
colsBreakpointColsOptional; defaults to DEFAULT_BREAKPOINT_COLS.
compactorCompactorUsed when generating a missing breakpoint’s layout.
onLayoutChange(layout, layouts) => voidActive layout + updated map.
onBreakpointChange(breakpoint, cols) => voidOn breakpoint change.

Returns the active breakpoint, its cols, the resolved layout, and an onLayoutChange to pass to the grid (it updates the active breakpoint’s entry).

resolveController

Resolves a grid’s controller by its group (= the grid’s id) from the registry, scoped to the ambient dnd-kit manager. The registry-based alternative to the context resolution that createGridItem uses internally; reach for it for advanced composition. Must be called during component initialization, inside a <DragDropProvider>.

<script lang="ts"> import { resolveController } from "@snapgridjs/svelte"; let { group } = $props(); const controller = resolveController(group); </script>

No overlay factory (tiles float themselves)

There’s no createGridDragOverlay — and no overlay component either. While you drag with a pointer, dnd-kit lifts the active tile into the browser’s top layer, so the tile you already rendered floats itself above everything and crosses between grids unclipped; on drop it settles into its new cell. Nothing extra to wire. Style the floating state through the tile’s own isDragging. See The dragged tile floats itself.

If you need a separate floating element — a clone, a ghost, different markup — dnd-kit’s DragOverlay is re-exported from @snapgridjs/svelte. It’s a component, not a factory, because it owns and renders its own element, so there’s nothing for a factory to return.

Last updated on