Dashboard Grid
Drag-and-resize dashboard widgets on a CSS grid with pure, collision-resolving layout functions.
| Package | @vizejs/ui/dashboard-grid |
| Maturity | stable |
| Own the source | vize lib pull dashboard-grid |
| Requires | context, controllable-state |
| Aliases | grid layout, draggable dashboard, widget grid, resizable widgets |
| Covers | react-grid-layout, gridstack.js, vue-grid-layout |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { DashboardGrid, DashboardGridItem } from "@vizejs/ui/dashboard-grid";
Or copy the source into your project with vize lib pull dashboard-grid (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Metrics dashboard whose widgets can be dragged and resized on a twelve-column grid. -->
<script setup lang="ts">
import { ref } from "vue";
import { DashboardGrid, DashboardGridItem } from "@vizejs/ui/dashboard-grid";
import type { DashboardLayout } from "@vizejs/ui/dashboard-grid";
const layout = ref<DashboardLayout>([
{ id: "revenue", x: 0, y: 0, w: 6, h: 2 },
{ id: "signups", x: 6, y: 0, w: 3, h: 2 },
{ id: "churn", x: 9, y: 0, w: 3, h: 2 },
]);
const titles: ReadonlyMap<string, string> = new Map([
["revenue", "Revenue"],
["signups", "Sign-ups"],
["churn", "Churn"],
]);
function titleOf(id: string): string {
return titles.get(id) ?? id;
}
</script>
<template>
<DashboardGrid v-model:layout="layout" :columns="12" :row-height="48" label="Metrics">
<DashboardGridItem
v-for="item in layout"
:id="item.id"
:key="item.id"
:label="titleOf(item.id)"
>
<template #default="{ handleProps, resizeHandleProps }">
<h3 v-bind="handleProps">{{ titleOf(item.id) }}</h3>
<p>{{ item.w }} × {{ item.h }} cells</p>
<span v-bind="resizeHandleProps">⤡</span>
</template>
</DashboardGridItem>
</DashboardGrid>
</template>
API
DashboardGrid
Source: src/families/layout/dashboard-grid/dashboard-grid.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
layout |
DashboardLayout |
undefined |
Controlled widget placement (v-model:layout). undefined selects uncontrolled behavior. |
defaultLayout |
DashboardLayout |
[] |
Initial uncontrolled placement. |
columns |
number |
12 |
Number of grid columns. |
rowHeight |
number |
48 |
Height of one grid row in CSS pixels. |
gap |
number |
8 |
Gap between cells in CSS pixels. |
compaction |
DashboardCompaction |
"vertical" |
Compaction after each change: pull widgets up ("vertical") or leave gaps ("none"). |
disabled |
boolean |
false |
Disable dragging and resizing. |
label |
string |
undefined |
Accessible name of the dashboard region. |
Events
| Event | Payload | Description |
|---|---|---|
update:layout |
[layout: DashboardLayout] |
Fired with the next placement after a widget moves or resizes. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
DashboardGridSlotState |
DashboardGridItem children. Receives the occupied row count. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
layout |
— | |
rows |
— |
DashboardGridItem
Source: src/families/layout/dashboard-grid/dashboard-grid-item.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id (required) |
string |
required |
Widget id matching its layout entry. |
label |
string |
undefined |
Accessible name of the widget. |
defaultPosition |
Omit<DashboardItem, "id"> |
{ x: 0, y: 0, w: 1, h: 1 } |
Placement used until the layout contains this widget. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
DashboardGridItemSlotState |
Widget content. Receives placement, drag state, and handle props. |
Exposed
| Member | Type | Description |
|---|---|---|
item |
— | |
dragging |
— |
Behavior
Normative state x input -> outcome table for dashboard-grid.vue and
dashboard-grid-item.vue (@vizejs/ui/dashboard-grid). Rows are proven by
dashboard-grid-layout.test.ts, dashboard-grid.test.ts, and
dashboard-grid-ssr.test.ts; compile-only assertions live in
dashboard-grid.types.test-d.ts.
| # | State | Input | Outcome | Proven by |
|---|---|---|---|---|
| DG1 | two items | collision check | overlapping cells collide; an item never collides with itself | detects overlapping cells but never self-collision |
| DG2 | raw layout | normalize | integer cells, sizes within min/max and columns, x inside the grid | normalizes sizes, constraints, and positions into the grid |
| DG3 | gaps / overlaps | compact | items rise until blocked (static items never move); "none" only pushes overlaps down |
compacts upward until blocked by static widgets and resolves overlaps |
| DG4 | layout | move | the mover takes its cell, colliding items are pushed down, others compact; no-ops return the input | moves push colliding widgets down and compact the rest |
| DG5 | static widgets | move onto / move static | the layout is returned unchanged | moves onto static widgets are rejected and static widgets never move |
| DG6 | layout | resize | size respects min/max and remaining columns; neighbours are pushed; "none" skips compaction |
resizes within constraints and pushes neighbours |
| DG7 | layout | rows | the occupied row count is reported | reports the number of occupied rows |
| DG8 | default | render | a labelled region with a native CSS grid; widgets are labelled groups on grid lines | renders a labelled CSS grid with widgets on native grid lines |
| DG9 | focused handle | arrows, Shift+arrows | move and resize by one cell with collision handling; static widgets and other keys are ignored | keyboard moves and resizes widgets and pushes collisions |
| DG10 | measured grid | pointer drag / resize grip | pointer deltas snap to whole cells; data-dragging is set while active |
pointer drags snap to cells using the measured cell size |
| DG11 | disabled / controlled | input | disabled grids ignore input; controlled layouts only change via the prop | disabled grids ignore input and controlled layouts only change via props |
| DG12 | no provider | mount a widget | stable VIZE_UI_CONTEXT_MISSING: DashboardGrid diagnostic |
widgets outside a grid throw the context diagnostic |
| DG13 | SSR | isolated requests, hydrate | byte-identical grid-line placement; hydration without warnings | renders byte-identical grid placement across SSR requests, hydrates the grid without mismatch warnings |
| DG14 | DOM/SSR/Vapor | compile | both SFCs compile in every renderer lane | scripts/check-renderers.ts |
Placement is pure CSS grid (grid-column/grid-row spans), so server output
is final layout; pointer input only needs the measured grid width.