Use the dashboard kit when your UI is a board of widgets arranged in cells. You declare views and widgets as data; the kit mounts the board, paints the built-in widget kinds, and owns drag, resize, and undo.
Before you start
Install the package for your framework and the shared engine and renderer. The examples below use the package versions supported by Grafloria 0.4:
bashnpm install @grafloria/element @grafloria/engine @grafloria/renderer
For React, Vue, Angular, or Qwik, also install that framework binding. The bindings support React 17–19, Vue 3.4 or later, and Angular 18.1 or later.
1. Declare the board
Start with DashboardViewSpec objects. A view
contains widgets; a widget needs an id and can specify a kind, span, rows, title, and
data. If you omit x and y, widgets flow in declaration order. The built-in renderers draw
kpi, line, bar, donut, funnel, and table widgets from their data.
The browser shows Revenue and Customers KPI cards, a Revenue trend chart, and a Revenue by region chart. Give the mounted element a height; a board in a zero-height container has no space to draw.
jsimport { dashboard, render } from '@grafloria/element';
const views = [{
id: 'overview',
name: 'Overview',
widgets: [
{ id: 'revenue', kind: 'kpi', span: 3, rows: 1,
data: { label: 'Revenue', value: '$6.81M', delta: 12.4, spark: [42, 45, 51, 61, 76] } },
{ id: 'customers', kind: 'kpi', span: 3, rows: 1,
data: { label: 'Customers', value: '1,284', delta: 8.1, spark: [980, 1010, 1090, 1150, 1284] } },
{ id: 'trend', kind: 'line', span: 6, rows: 2, title: 'Revenue trend',
data: { series: [{ name: 'Revenue', values: [42, 45, 51, 61, 76] }], labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'] } },
{ id: 'regions', kind: 'donut', span: 6, rows: 2, title: 'Revenue by region',
data: { slices: [{ label: 'EMEA', value: 1.92 }, { label: 'APAC', value: 1.34 }] } },
],
}];
const spec = dashboard({ views, columns: 12, sizing: 'fit' });
const container = document.getElementById('dashboard');
if (!container) throw new Error('Missing #dashboard');
container.style.height = '500px';
const instance = render(spec, container);
tsximport { GrafloriaDashboard } from '@grafloria/react';
import type { DashboardViewSpec } from '@grafloria/element';
const views: DashboardViewSpec[] = [{ id: 'overview', name: 'Overview', widgets: [
{ id: 'revenue', kind: 'kpi', span: 3, rows: 1, data: { label: 'Revenue', value: '$6.81M', delta: 12.4, spark: [42, 45, 51, 61, 76] } },
{ id: 'customers', kind: 'kpi', span: 3, rows: 1, data: { label: 'Customers', value: '1,284', delta: 8.1, spark: [980, 1010, 1090, 1150, 1284] } },
{ id: 'trend', kind: 'line', span: 6, rows: 2, title: 'Revenue trend', data: { series: [{ name: 'Revenue', values: [42, 45, 51, 61, 76] }], labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'] } },
{ id: 'regions', kind: 'donut', span: 6, rows: 2, title: 'Revenue by region', data: { slices: [{ label: 'EMEA', value: 1.92 }, { label: 'APAC', value: 1.34 }] } },
]}];
export default function Dashboard() {
return <GrafloriaDashboard views={views} options={{ columns: 12, sizing: 'fit' }} style={{ display: 'block', height: 500 }} />;
}
vue<script setup lang="ts"> import { ref } from 'vue'; import { GrafloriaDashboard } from '@grafloria/vue'; import type { DashboardViewSpec } from '@grafloria/element'; const tab = ref('overview'); const views: DashboardViewSpec[] = [{ id: 'overview', name: 'Overview', widgets: [ { id: 'revenue', kind: 'kpi', span: 3, rows: 1, data: { label: 'Revenue', value: '$6.81M', delta: 12.4, spark: [42, 45, 51, 61, 76] } }, { id: 'customers', kind: 'kpi', span: 3, rows: 1, data: { label: 'Customers', value: '1,284', delta: 8.1, spark: [980, 1010, 1090, 1150, 1284] } }, { id: 'trend', kind: 'line', span: 6, rows: 2, title: 'Revenue trend', data: { series: [{ name: 'Revenue', values: [42, 45, 51, 61, 76] }], labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'] } }, { id: 'regions', kind: 'donut', span: 6, rows: 2, title: 'Revenue by region', data: { slices: [{ label: 'EMEA', value: 1.92 }, { label: 'APAC', value: 1.34 }] } }, ]}]; </script> <template> <GrafloriaDashboard :views="views" :options="{ columns: 12, sizing: 'fit' }" v-model:active-view="tab" style="display:block;height:500px" /> </template>
tsimport { Component } from '@angular/core';
import { GrafloriaDashboardComponent } from '@grafloria/angular';
import type { DashboardViewSpec } from '@grafloria/element';
@Component({
standalone: true,
imports: [GrafloriaDashboardComponent],
template: `<grafloria-dashboard [views]="views" [options]="options" [(activeView)]="tab" style="display:block;height:500px" />`,
})
export class DashboardComponent {
tab: string | undefined = 'overview';
options = { columns: 12, sizing: 'fit' as const };
views: DashboardViewSpec[] = [{ id: 'overview', name: 'Overview', widgets: [
{ id: 'revenue', kind: 'kpi', span: 3, rows: 1, data: { label: 'Revenue', value: '$6.81M', delta: 12.4, spark: [42, 45, 51, 61, 76] } },
{ id: 'customers', kind: 'kpi', span: 3, rows: 1, data: { label: 'Customers', value: '1,284', delta: 8.1, spark: [980, 1010, 1090, 1150, 1284] } },
{ id: 'trend', kind: 'line', span: 6, rows: 2, title: 'Revenue trend', data: { series: [{ name: 'Revenue', values: [42, 45, 51, 61, 76] }], labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'] } },
{ id: 'regions', kind: 'donut', span: 6, rows: 2, title: 'Revenue by region', data: { slices: [{ label: 'EMEA', value: 1.92 }, { label: 'APAC', value: 1.34 }] } },
]}];
}
tsximport { component$ } from '@builder.io/qwik';
import { GrafloriaDashboard } from '@grafloria/qwik';
import type { DashboardViewSpec } from '@grafloria/element';
const views: DashboardViewSpec[] = [{ id: 'overview', name: 'Overview', widgets: [
{ id: 'revenue', kind: 'kpi', span: 3, rows: 1, data: { label: 'Revenue', value: '$6.81M', delta: 12.4, spark: [42, 45, 51, 61, 76] } },
{ id: 'customers', kind: 'kpi', span: 3, rows: 1, data: { label: 'Customers', value: '1,284', delta: 8.1, spark: [980, 1010, 1090, 1150, 1284] } },
{ id: 'trend', kind: 'line', span: 6, rows: 2, title: 'Revenue trend', data: { series: [{ name: 'Revenue', values: [42, 45, 51, 61, 76] }], labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'] } },
{ id: 'regions', kind: 'donut', span: 6, rows: 2, title: 'Revenue by region', data: { slices: [{ label: 'EMEA', value: 1.92 }, { label: 'APAC', value: 1.34 }] } },
]}];
export default component$(() => <GrafloriaDashboard views={views} options={{ columns: 12, sizing: 'fit' }} style={{ display: 'block', height: '500px' }} />);
The mounted board shows the KPI cards and charts, and the user can drag or resize them. views
and widgets are mutually exclusive: use views for tabs, or widgets as the shorthand for one
unnamed view.
2. Change the live layout
Keep the typed DashboardHandle returned by
the JavaScript dashboard() spec or emitted by a framework binding. The handle changes the live
board without rebuilding it:
tsimport type { DashboardHandle } from '@grafloria/element';
function configure(handle: DashboardHandle): void {
handle.setSizing('grow');
handle.setLayout('split');
handle.setColumns(6);
handle.setRtl(true);
handle.setStatic(false);
}
'fit' keeps the board bounded and squeezes rows; 'grow' keeps the row height and extends the
board. setLayout('split') changes the cell grid to the splitter layout. setColumns() changes
the live column count, and setRtl(true) mirrors pixels without changing cell data. A static board
has no pointer drag, resize, or handles.
The component props layout, sizing, and static are live. Other declaration data is read at
mount; after mount, use the handle for changes.
3. Persist the resulting board
Use DashboardSnapshot data from toJSON() as the
persisted value. It contains the live views, cells, columns, gap, sizing, float, and direction.
tsimport { dashboard, render } from '@grafloria/element';
import type { DashboardSnapshot } from '@grafloria/element';
const saved = localStorage.getItem('sales-board');
const snapshot = saved ? JSON.parse(saved) as DashboardSnapshot : undefined;
const { views, ...options } = snapshot ?? {
views: [{ id: 'overview', widgets: [{ id: 'revenue', kind: 'kpi', data: { label: 'Revenue', value: '$6.81M' } }] }],
columns: 12,
sizing: 'fit' as const,
};
const spec = dashboard({ ...options, views });
const container = document.getElementById('dashboard');
if (!container) throw new Error('Missing #dashboard');
container.style.height = '500px';
render(spec, container);
const handle = spec.handle;
localStorage.setItem('sales-board', JSON.stringify(handle.toJSON()));
The save call reads the live board, so a user’s moves, resizes, view changes, and layout switches are included. In a framework binding, connect its layout-change event to the same persistence call:
tsimport type { DashboardHandle } from '@grafloria/element';
function persist(handle: DashboardHandle): void {
localStorage.setItem('sales-board', JSON.stringify(handle.toJSON()));
}
Do not serialize the render functions. On restore, pass the saved data back to dashboard() and
provide renderWidget again if you supplied one.
Options that matter
| Option | Type | Default | What it does |
|---|---|---|---|
columns | number | 12 | Sets the column count for every view. |
gap | number | 8 | Sets widget spacing and board padding in pixels. |
sizing | 'fit' | 'grow' | 'grow' | Bounds the board or lets it extend by row height. |
layout | 'grid' | 'split' | 'grid' | Selects cell packing or splitter layout. |
rowHeight | number | 130 | Sets row height in grow mode, in pixels. |
static | boolean | false | Disables pointer dragging, resizing, and handles. |
rtl | boolean | false | Mirrors rendered columns while keeping cell coordinates. |
responsive | DashboardResponsiveOptions | — | Derives live columns from board width. |
Pitfalls
- Give the dashboard element an explicit height.
- Use
DashboardHandleafter mount; changingviewslater does not rebuild the board. - In fit mode, an add, move, or resize that needs more capacity is refused. Use grow mode or
overflow: 'scroll'when the board must retain its row height. viewsandwidgetscannot be supplied together.- Use
widgetTypesfor custom React widgets andgrafloriaWidgettemplates for custom Angular widgets. Otherwise the shipped built-in painters handle the six built-in kinds.
See it running
- Dashboard builder — tabbed views, built-in widgets, live layout controls, and save/load.
- Fluid board — fit/grow and grid/split switches.
- Dashboard grid options — responsive columns, RTL, sections, and pinned widgets.
Related: Dashboards in JavaScript, Dashboards in React, and Dashboards in Angular.
Was this page helpful?