Vize

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.