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.
- π― Svelte 5 Native - Built with modern runes (
$state,$props,$bindable) and snippets - π Lightweight Wrapper - Minimal abstraction over GridStack.js for maximum performance
- π¦ TypeScript Support - Full TypeScript definitions included
- π¨ Customizable - Complete access to GridStack.js options and methods
- π± Responsive - Mobile-friendly with touch support
- π§ Simple API - Easy to use with declarative Svelte components
npm 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 components
Individual 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 ifx/ynot providedchildren- Content to display inside the grid item
- Purpose: The overall grid container that manages the layout system
- What it does: Sets up the coordinate system, handles grid calculations
- Don't touch: Let GridStack manage this completely
- Purpose: The absolutely positioned element that GridStack moves around
- What it does:
- Handles positioning (
top,left,width,height) - Uses CSS variables for grid calculations (
--gs-column-width,--gs-cell-height) - Contains resize handles as direct children
- Manages all animations and transitions during drag/resize
- Handles positioning (
- Don't touch: Position, dimensions, padding, transforms, transitions
- Never add: Any CSS that affects positioning or animation
- Purpose: Creates the visual gaps between widgets
- What it does:
- Uses
position: absolutewithtop,right,bottom,left - These positions reference CSS variables (
--gs-item-margin-*) to create gaps - Acts as a "negative space" creator
- Uses
- NEVER style this: Any styling breaks GridStack's margin system
- Don't add:
height: 100%,display: flex, backgrounds, borders, etc.
- Purpose: Your actual widget content
- What goes here: ALL your visual styling
- Backgrounds, borders, shadows
- Padding for internal spacing
- Layout properties (flex, grid)
height: 100%to fill the available space
- Best practice: Use a single wrapper div as the immediate child
GridStack uses a "negative space" approach for margins:
- The
.grid-stack-itemis the full cell size - The
.grid-stack-item-contentis positioned inside with offsets - The gap between items is created by these offsets, not by margins
This 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 checkMIT
Contributions are welcome! Please feel free to submit a Pull Request.