A lightweight, modern Svelte 5 wrapper for GridStack.js v12 - create draggable, resizable dashboard layouts with ease.
This library provides thin, performant Svelte 5 components that wrap GridStack.js v12, enabling you to build interactive dashboards with drag-and-drop functionality, responsive grids, and dynamic layouts. Built with Svelte's latest runes and snippets syntax for optimal performance.
$state
, $props
, $bindable
) and snippetsnpm install gridstack.svelte
# or
pnpm add gridstack.svelte
<script lang="ts">
import Grid, { GridItem } from 'gridstack.svelte';
import 'gridstack/dist/gridstack.min.css';
</script>
<Grid>
<GridItem x={0} y={0} w={4} h={2}>Widget 1</GridItem>
<GridItem x={4} y={0} w={4} h={4}>Widget 2</GridItem>
<GridItem x={8} y={0} w={2} h={2}>Widget 3</GridItem>
</Grid>
<script lang="ts">
import Grid, { GridItem } from 'gridstack.svelte';
import { GridStack } from 'gridstack';
import 'gridstack/dist/gridstack.min.css';
let grid = $state.snapshot(null);
let options = $state({
margin: 8,
cellHeight: 70,
acceptWidgets: true,
removable: '#trash'
});
$effect(() => {
// Setup drag-in from external elements
GridStack.setupDragIn('.newWidget', undefined, [{ w: 2, h: 2, content: 'New Item' }]);
});
</script>
<Grid ref={grid} {options}>
<GridItem id="w1" x={0} y={0} w={4} h={2}>Dashboard Widget</GridItem>
<GridItem id="w2" x={4} y={0} w={4} h={4} noResize={true} locked={true}>
Locked Widget (Can't resize or move)
</GridItem>
<GridItem id="w3" x={8} y={0} w={2} h={2} noMove={true}>Can resize but not move</GridItem>
</Grid>
The main container component that initializes GridStack.
Props:
ref
- Bindable reference to the GridStack instanceoptions
- GridStack configuration options (margin, cellHeight, etc.)children
- Snippet containing GridItem componentsIndividual grid items that can be dragged and resized.
Props:
id
- Unique identifier for the itemx
, y
- Grid position (0-based)w
, h
- Width and height in grid unitsminW
, maxW
- Min/max width constraintsminH
, maxH
- Min/max height constraintsnoResize
- Disable resizingnoMove
- Disable movinglocked
- Lock item (no resize, move, or drag)autoPosition
- Auto-position item if x
/y
not providedchildren
- Content to display inside the grid item.grid-stack
- The Container.grid-stack-item
- The Positioned Wrappertop
, left
, width
, height
)--gs-column-width
, --gs-cell-height
).grid-stack-item-content
- The Margin/Gap Managerposition: absolute
with top
, right
, bottom
, left
--gs-item-margin-*
) to create gapsheight: 100%
, display: flex
, backgrounds, borders, etc..grid-stack-item-content
height: 100%
to fill the available spaceGridStack uses a "negative space" approach for margins:
.grid-stack-item
is the full cell size.grid-stack-item-content
is positioned inside with offsetsThis is why styling .grid-stack-item-content
breaks everything - you're interfering with the positioning layer that creates the gaps.
<div class="grid-stack"> <!-- GridStack manages -->
<div class="grid-stack-item"> <!-- GridStack positions -->
<div class="grid-stack-item-content"> <!-- GridStack gaps - DON'T TOUCH -->
<div class="your-widget"> <!-- YOUR styling goes here -->
<!-- Your content -->
</div>
</div>
</div>
</div>
The mistake we made was trying to "help" GridStack by styling its internal structure, when we should have just styled our own content and let GridStack handle its layout mechanics.
# Install dependencies
pnpm install
# Run development server
pnpm dev
# Build library
pnpm build
# Run tests
pnpm test
# Type checking
pnpm check
MIT
Contributions are welcome! Please feel free to submit a Pull Request.