A Vue 3 controlled grid layout component built on Gridstack.js for draggable, resizable, responsive dashboards and workbenches.
English | 简体中文
vue-gridstack wraps Gridstack.js with a Vue-first controlled data model. Your application owns the layout through v-model:layout, while GridstackItem keeps Vue slot content stable and lets Gridstack handle drag, resize, collision, and responsive behavior.
This keeps dashboard state easy to persist, restore, diff, and synchronize with your own data model.
- Vue 3 component API with named exports and plugin installation.
- Controlled layout through
v-model:layout. - Stable slot rendering for arbitrary Vue components inside widgets.
- Drag, resize, static, locked, min/max size, auto-position, and sub-grid options.
- Built-in
heightMode="fill"for fixed-height dashboards, video walls, and control rooms. - Gridstack event forwarding with the calculated next layout.
- Typed public API and exported TypeScript types.
- Access to the underlying
GridStackinstance for advanced use cases.
Install the Vue wrapper:
npm install vue-gridstackpnpm add vue-gridstackyarn add vue-gridstackvue-gridstack imports the required component styles and Gridstack styles from its entry. If your bundler needs an explicit style entry, import it once in your app:
import "vue-gridstack/style.css";Runtime dependencies:
| Package | Version |
|---|---|
gridstack |
Bundled with vue-gridstack |
Peer dependencies:
| Package | Version |
|---|---|
vue |
^3.5.0 |
<template>
<Gridstack v-model:layout="layout" :options="gridOptions">
<GridstackItem id="sales">
<SalesCard />
</GridstackItem>
<GridstackItem id="traffic">
<TrafficChart />
</GridstackItem>
</Gridstack>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Gridstack, GridstackItem } from "vue-gridstack";
import type { GridstackLayoutItem, GridstackOptions } from "vue-gridstack";
const gridOptions = ref<GridstackOptions>({
column: 12,
margin: 8,
disableDrag: false,
disableResize: false
});
const layout = ref<GridstackLayoutItem[]>([
{ id: "sales", x: 0, y: 0, w: 4, h: 3 },
{ id: "traffic", x: 4, y: 0, w: 8, h: 3 }
]);
</script>Global registration is also supported:
import { createApp } from "vue";
import VueGridstack from "vue-gridstack";
import App from "./App.vue";
createApp(App).use(VueGridstack).mount("#app");Use this pattern for new code:
Gridstackreceives container-level options such ascolumn,margin,staticGrid,disableDrag, anddisableResize.Gridstackowns widget geometry throughv-model:layout.GridstackItemonly receives a stableidand renders your Vue slot content.- Each
GridstackItem idmust match an item inlayout.
<Gridstack v-model:layout="layout" :options="{ column: 12 }">
<GridstackItem v-for="widget in widgets" :key="widget.id" :id="widget.id">
<WidgetCard :widget="widget" />
</GridstackItem>
</Gridstack>const layout = ref<GridstackLayoutItem[]>([
{ id: "cpu", x: 0, y: 0, w: 3, h: 2 },
{ id: "memory", x: 3, y: 0, w: 3, h: 2, minW: 2 },
{ id: "logs", w: 6, h: 3, autoPosition: true }
]);| Prop | Type | Default | Description |
|---|---|---|---|
layout |
GridstackLayoutItem[] |
[] |
Controlled widget layout. Prefer v-model:layout. |
options |
GridstackOptions |
{} |
Container-level Gridstack options passed to GridStack.init() and grid.updateOptions(). |
heightMode |
"native" | "fill" |
"native" |
Controls how the component handles grid height and cellHeight. |
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | number |
Required | Stable widget id. Must match the corresponding layout item. |
Do not pass geometry props such as x, y, w, h, noResize, or noMove to GridstackItem. Put widget configuration in layout.
<!-- Recommended -->
<Gridstack v-model:layout="layout">
<GridstackItem id="chart">
<ChartPanel />
</GridstackItem>
</Gridstack>const layout = ref([{ id: "chart", x: 0, y: 0, w: 4, h: 3 }]);Every item needs a stable id. The component forwards the following widget fields to Gridstack:
| Field | Type | Description |
|---|---|---|
id |
string | number |
Unique widget id, normalized to string internally. |
x |
number | string |
Column position, starting at 0. |
y |
number | string |
Row position, starting at 0. |
w |
number | string |
Width in columns. Normalized to at least 1. |
h |
number | string |
Height in rows. Normalized to at least 1. |
autoPosition |
boolean | string |
Lets Gridstack place the widget automatically. When true, x and y are ignored until placement is committed. |
minW / maxW |
number | string |
Minimum and maximum width. |
minH / maxH |
number | string |
Minimum and maximum height. |
noResize |
boolean | string |
Prevents this widget from being resized. |
noMove |
boolean | string |
Prevents this widget from being dragged. |
locked |
boolean | string |
Locks this widget according to Gridstack behavior. |
content |
string |
Native Gridstack content field. Slot content is recommended for Vue. |
sizeToContent |
boolean | number | string |
Forwards Gridstack size-to-content behavior. |
lazyLoad |
boolean | string |
Native Gridstack lazy-load field. |
resizeToContentParent |
string |
Parent selector used by Gridstack content resizing. |
subGridOpts |
object |
Native Gridstack sub-grid options. |
Layout update rules:
- Parent changes to
layoutare synchronized into Gridstack. - Drag and resize changes emit
update:layout. - Existing business fields on layout items are preserved when the component writes back
x,y,w, andh. autoPosition: trueis removed after Gridstack resolves the final position.- Fields not listed above can live in
layout, but they are not passed to Gridstack.
options accepts native Gridstack container options. The component adds one default:
{
auto: false
}auto: false prevents Gridstack from scanning DOM before Vue slot content is ready. Widgets are created and updated by the component based on layout and rendered GridstackItem nodes.
Common options:
const gridOptions = computed<GridstackOptions>(() => ({
column: 12,
margin: 8,
float: false,
animate: true,
disableDrag: !editing.value,
disableResize: !editing.value,
draggable: {
handle: ".grid-stack-item-content",
appendTo: "body"
},
resizable: {
handles: "all",
autoHide: true
}
}));The default mode. Grid height follows native Gridstack behavior and your options.cellHeight.
<Gridstack v-model:layout="layout" height-mode="native" :options="{ column: 12, cellHeight: 80 }">
<GridstackItem id="a">
<Widget />
</GridstackItem>
</Gridstack>Use this mode for normal page flow, long dashboards, and layouts that should grow naturally.
The component fills its parent height and calculates cellHeight from the parent height, row count, and margin.
<template>
<section class="dashboard">
<Gridstack v-model:layout="layout" height-mode="fill" :options="{ row: 6, column: 12 }">
<GridstackItem id="camera">
<CameraPlayer />
</GridstackItem>
</Gridstack>
</section>
</template>
<style scoped>
.dashboard {
height: 100%;
min-height: 560px;
}
</style>Use this mode for fixed canvases, video walls, monitoring screens, and workbenches that must fill a known container. The parent element must have an explicit height. Setting options.row or options.minRow is recommended.
Gridstack forwards common Gridstack events. The change event also includes the computed nextLayout.
| Event | Payload | Description |
|---|---|---|
ready |
(grid) |
Gridstack instance is initialized. |
change |
(items, event, grid, nextLayout) |
Layout changed. nextLayout is the computed controlled layout. |
added |
(items, event, grid) |
Widgets were added. |
removed |
(items, event, grid) |
Widgets were removed. |
dragstart |
(element, event, grid) |
Drag started. |
drag |
(element, event, grid) |
Dragging. |
dragstop |
(element, event, grid) |
Drag stopped. |
resizestart |
(element, event, grid) |
Resize started. |
resize |
(element, event, grid) |
Resizing. |
resizestop |
(element, event, grid) |
Resize stopped. |
resizecontent |
(items, event, grid) |
Gridstack content resize event. |
dropped |
(previousNode, newNode, event, grid) |
External item was dropped into the grid. |
enable |
(event, grid) |
Gridstack was enabled. |
disable |
(event, grid) |
Gridstack was disabled. |
<Gridstack v-model:layout="layout" @ready="onReady" @change="onChange">
<GridstackItem id="a">
<Widget />
</GridstackItem>
</Gridstack>function onReady(grid: GridStack) {
grid.compact();
}
function onChange(items, event, grid, nextLayout) {
console.log(nextLayout);
}You usually do not need to assign layout manually in change; v-model:layout already receives updates. Use change for persistence, analytics, or side effects.
Use a template ref to access the public methods:
<template>
<Gridstack ref="gridRef" v-model:layout="layout">
<GridstackItem id="a">
<Widget />
</GridstackItem>
</Gridstack>
</template>
<script setup lang="ts">
import { ref } from "vue";
import type { GridstackExpose } from "vue-gridstack";
const gridRef = ref<GridstackExpose | null>(null);
function saveCurrentLayout() {
const nextLayout = gridRef.value?.getLayout() ?? [];
return api.saveLayout(nextLayout);
}
function compact() {
gridRef.value?.getGridStack()?.compact();
}
</script>| Method | Returns | Description |
|---|---|---|
getGridStack() |
GridStack | null |
Returns the native Gridstack instance. |
getGridElement() |
HTMLElement | null |
Returns the inner .grid-stack-inner element. |
getShellElement() |
HTMLElement | null |
Returns the outer shell element. |
syncWidgets() |
void |
Manually synchronizes rendered widgets with Gridstack. |
updateGridCellHeight() |
void |
Recalculates cellHeight in heightMode="fill". |
getLayout() |
GridstackLayoutItem[] |
Reads the current layout from Gridstack. |
getOptions() |
GridstackOptions |
Returns the resolved Gridstack options used by the component. |
<Gridstack v-model:layout="layout" :options="{ column: 12, staticGrid: true }">
<GridstackItem v-for="item in widgets" :key="item.id" :id="item.id">
<WidgetCard :data="item" />
</GridstackItem>
</Gridstack>You can also use disableDrag and disableResize:
<Gridstack
v-model:layout="layout"
:options="{ column: 12, disableDrag: true, disableResize: true }"
>
<GridstackItem id="a">
<Widget />
</GridstackItem>
</Gridstack><template>
<button type="button" @click="editing = !editing">Toggle edit</button>
<Gridstack v-model:layout="layout" :options="gridOptions" @change="dirty = true">
<GridstackItem v-for="item in widgets" :key="item.id" :id="item.id">
<WidgetCard :data="item" />
</GridstackItem>
</Gridstack>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import type { GridstackOptions } from "vue-gridstack";
const editing = ref(false);
const dirty = ref(false);
const gridOptions = computed<GridstackOptions>(() => ({
column: 12,
margin: 8,
disableDrag: !editing.value,
disableResize: !editing.value,
resizable: { handles: "all" }
}));
</script>Add both the business item and its layout item. Use autoPosition: true when you want Gridstack to find a place.
function addWidget() {
const id = `widget-${Date.now()}`;
widgets.value.push({
id,
title: "New widget"
});
layout.value.push({
id,
w: 3,
h: 2,
autoPosition: true
});
}After Gridstack places the widget, the component writes back the real x, y, w, and h.
Keep your rendered list and layout in sync:
function removeWidget(id: string) {
widgets.value = widgets.value.filter(item => item.id !== id);
layout.value = layout.value.filter(item => String(item.id) !== id);
}const layout = ref<GridstackLayoutItem[]>([
{
id: "fixed-panel",
x: 0,
y: 0,
w: 4,
h: 3,
noMove: true,
noResize: true
}
]);<template>
<section class="video-wall">
<Gridstack v-model:layout="layout" height-mode="fill" :options="gridOptions">
<GridstackItem v-for="camera in cameras" :key="camera.id" :id="camera.id">
<CameraPlayer :camera="camera" />
</GridstackItem>
</Gridstack>
</section>
</template>
<script setup lang="ts">
import { ref } from "vue";
import type { GridstackOptions } from "vue-gridstack";
const gridOptions = ref<GridstackOptions>({
row: 2,
column: 4,
margin: 6,
float: true,
disableDrag: true,
disableResize: true
});
</script>
<style scoped>
.video-wall {
height: 100%;
min-height: 560px;
}
</style><Gridstack ref="gridRef" v-model:layout="layout" @ready="onReady">
<GridstackItem id="a">
<Widget />
</GridstackItem>
</Gridstack>function onReady(grid: GridStack) {
grid.compact();
}
function batchUpdate() {
const grid = gridRef.value?.getGridStack();
if (!grid) return;
grid.batchUpdate();
// call native Gridstack APIs here
grid.batchUpdate(false);
layout.value = gridRef.value?.getLayout() ?? layout.value;
}GridstackItem is recommended for Vue apps. For migration from native Gridstack markup, direct DOM nodes are also supported:
<Gridstack :options="{ column: 12 }">
<div class="grid-stack-item" gs-id="native-a" gs-x="0" gs-y="0" gs-w="4" gs-h="2">
<div class="grid-stack-item-content">Native A</div>
</div>
<div class="grid-stack-item" gs-id="native-b" gs-w="3" gs-h="2" gs-auto-position="true">
<div class="grid-stack-item-content">Native B</div>
</div>
</Gridstack>The component can read these attributes: gs-id, gs-x, gs-y, gs-w, gs-h, gs-min-w, gs-max-w, gs-min-h, gs-max-h, gs-auto-position, gs-no-resize, gs-no-move, gs-locked, and gs-size-to-content.
import type {
GridstackExpose,
GridstackHeightMode,
GridstackItemId,
GridstackLayoutItem,
GridstackOptions
} from "vue-gridstack";npm install
npm run dev
npm run typecheck
npm run build
npm run build:demo
npm run build:all
npm run pack:checkScript summary:
| Script | Description |
|---|---|
npm run dev |
Starts the local demo app. |
npm run typecheck |
Runs Vue and TypeScript type checking. |
npm run build |
Builds the npm library output into dist. |
npm run build:demo |
Builds the deployable demo into dist-demo. |
npm run build:all |
Builds both library and demo. |
npm run pack:check |
Checks the npm package contents with a dry run. |
Make sure the GridstackItem id has a matching layout item. If no layout item exists, the component creates a fallback { id, w: 1, h: 1, autoPosition: true }.
heightMode="fill" requires the parent element to have a real height. Add height, min-height, or a layout rule that gives the parent a stable height.
Render Vue content with slots instead of the native Gridstack content field.
Custom fields on existing layout items are preserved during write-back, but only supported Gridstack widget fields are forwarded to Gridstack.
