Ga naar hoofdinhoud

CnDashboardGrid

Low-level drag-and-drop grid layout engine powered by GridStack. Manages widget placement, drag, and resize interactions and emits layout changes for persistence. Does not handle widget rendering — the parent provides content via the #widget scoped slot.

Used internally by CnDashboardPage. Only use this directly if you need fine-grained control over the grid without the full dashboard page shell.

Requires: gridstack (^12.0.0) as a peer dependency — install it yourself (npm install gridstack@^12.0.0) and import its stylesheet from that same copy (e.g. import 'gridstack/dist/gridstack.min.css'). nc-vue no longer bundles GridStack or its CSS: GridStack's JS drives layout through CSS custom properties (e.g. --gs-column-width) that only its own matching stylesheet reads, so the JS and CSS must come from the exact same installed version — a version mismatch is what previously made every grid item render at 0px width.

Try it​

Loading CnDashboardGrid playground…

Usage​

<CnDashboardGrid
:layout="placements"
:editable="isEditing"
:columns="12"
:cell-height="80"
@layout-change="onLayoutChange">
<template #widget="{ item }">
<MyWidget :config="item" />
</template>
</CnDashboardGrid>
// Layout item shape
const placements = [
{ id: 1, gridX: 0, gridY: 0, gridWidth: 6, gridHeight: 3 },
{ id: 2, gridX: 6, gridY: 0, gridWidth: 6, gridHeight: 3 },
]

function onLayoutChange(updated) {
// updated is the full layout array with new x/y/w/h values
saveLayout(updated)
}

Props​

PropTypeRequiredDefaultDescription
layoutArray✓—Array of layout items: { id, gridX, gridY, gridWidth, gridHeight, ...extra }
editableBooleanfalseEnables drag and resize interactions
columnsNumber12Number of grid columns
cellHeightNumber80Cell height in pixels
marginNumber12Gap between grid items in pixels
minWidthNumber2Minimum widget width in grid units
minHeightNumber2Minimum widget height in grid units
columnOptsObjectnullGridStack v12 responsive columnOpts bag; when set the grid reflows its column count across screen sizes. Build it with getDashboardColumnOpts. Default null = fixed columns.
cellHeightCssVarStringnullWhen set, cellHeight is mirrored into this CSS custom property on the document root at init (e.g. --app-cell-height). Default null = none.
itemKeyFunctionnullOptional (item) => string|number to derive each item's render key; forces a re-render when an item changes in a way its id doesn't capture (e.g. style edits). Default null = key on item.id.
keyboardRepositioningBooleantrueMakes grid items keyboard-operable: focusable in edit mode and repositionable/resizable with the arrow keys (WCAG 2.1 SC 2.1.1). See Keyboard operation.
itemLabelFunctionnullOptional (item, index) => string returning a grid item's accessible name; used verbatim. Default null = derived from the item (title → name → label → widgetTitle → widgetId → "Widget N").
activateOpensContextMenuBooleantrueWhether Enter/Space on a focused item also dispatches a bubbling contextmenu event from inside it, so right-click widget menus become keyboard-reachable without extra wiring.

Events​

EventPayloadDescription
layout-changelayout[]Emitted when any item is dragged, resized, or moved with the keyboard; payload is the full updated layout array
item-activate{ item, element, clientX, clientY }Emitted when the user presses Enter or Space on a focused grid item. The coordinates are anchored to the item's top-left corner so a consumer menu positions itself the same way it does for a right-click

Slots​

SlotScopeDescription
widget{ item }Content to render inside each grid cell; item is the layout object

Keyboard operation and accessibility​

GridStack's drag and resize gestures are pointer-only. CnDashboardGrid ships the keyboard equivalent required by WCAG 2.1 SC 2.1.1, enabled by default:

  • Every grid item is an ARIA group with an accessible name. In edit mode the name also carries the item's grid coordinates ("Revenue, column 5 of 12, row 1, 4 columns wide, 2 rows tall") — without them a screen-reader user tabbing an edit-mode dashboard has no idea where anything sits.
  • In edit mode (editable and keyboardRepositioning) each item is a tab stop with a visible focus ring, described by a shared, visually-hidden key map.
  • Move and resize results are spoken through a polite live region.

With a grid item focused:

KeyAction
ArrowLeft / ArrowRightMove one column left / right
ArrowUp / ArrowDownMove one row up / down
Shift + ArrowLeft / ArrowRightShrink / grow width by one column
Shift + ArrowUp / ArrowDownShrink / grow height by one row
Home / EndJump to the first / last column of the current row
Enter / SpaceActivate — emits item-activate and (by default) a synthetic contextmenu

Keys are only honoured while the grid item element itself holds focus, so buttons, inputs and menus rendered inside the #widget slot keep their own key handling.

Every keyboard change is applied with GridStack.update() — the same engine call drag and resize end in — so collision handling, the layout-change payload and the consumer's persistence are identical for both input modes. There is deliberately no second, keyboard-only update path.

Wiring a widget menu to the keyboard​

Consumers that open a per-widget menu on right-click get the keyboard path for free: Enter dispatches a bubbling contextmenu from inside the item, anchored to its top-left corner, exactly as browsers do for the Menu key. Prefer the explicit event in new code:

<CnDashboardGrid
:layout="placements"
:editable="isEditing"
:activate-opens-context-menu="false"
@item-activate="({ item, clientX, clientY }) => openMenu(item, clientX, clientY)"
@layout-change="onLayoutChange" />

Reference (auto-generated)​

The tables below are generated from the SFC source via vue-docgen-cli. They reflect what's actually in CnDashboardGrid.vue and update automatically whenever the component changes.

Props​

NameTypeRequiredDefaultDescription
layoutarray✓—Array of layout items: { id, gridX, gridY, gridWidth, gridHeight, ...extra }
editablebooleanfalseWhether drag and resize are enabled
columnsnumber12Number of grid columns
cellHeightnumber80Cell height in pixels
marginnumber12Grid margin in pixels
minWidthnumber2Minimum widget width in grid units
minHeightnumber2Minimum widget height in grid units
columnOptsunionnullGridStack v12 responsive columnOpts bag (breakpoints + reflow layout). When set, the grid reflows column count across screen sizes. Build it with getDashboardColumnOpts(). Default null = fixed columns, no responsive reflow (backwards-compatible).
cellHeightCssVarunionnullWhen set, cellHeight is mirrored into this CSS custom property on the document root at init (e.g. '--app-cell-height'), so app CSS can align to the grid geometry. Default null = no CSS var written.
itemKeyunionnullOptional (item) =&gt; string|number to derive each item's render key. Use it to force a re-render when an item changes in a way its id doesn't capture (e.g. style edits — return \${item.id}:\${item.updatedAt}). Default null = key on item.id.
keyboardRepositioningbooleantrueWhether grid items are keyboard-operable: focusable in edit mode and repositionable/resizable with the arrow keys (WCAG 2.1 SC 2.1.1 — the keyboard equivalent of the pointer-only GridStack drag). Turning it off leaves the items non-focusable and drag-only. Default true.
itemLabelunionnullOptional (item, index) =&gt; string returning the accessible name for a grid item. Use it when the layout items don't carry a human-readable field (the built-in fallback walks title → name → label → widgetTitle → widgetId, then a positional "Widget N"). The returned string is used verbatim — no coordinates are appended.
activateOpensContextMenubooleantrueWhether a keyboard activation (Enter / Space on a focused grid item) also dispatches a bubbling contextmenu event from inside the item, so consumers that open a per-widget menu on right-click get the keyboard path for free — mirroring what browsers do for the Menu key. Set false to rely purely on the item-activate event. Default true.

Events​

NamePayloadDescription
layout-change—Fired whenever the grid geometry changed — by drag, by resize, or by a keyboard nudge. Payload is the full layout array with the moved items' gridX/gridY/gridWidth/gridHeight updated.
item-activate—Fired when the user activates a focused grid item with Enter or Space. Payload { item, element, clientX, clientY } — the layout item, its DOM element, and viewport coordinates anchored to the item's top-left corner so a consumer menu can position itself the same way it does for a right-click.

Slots​

NameBindingsDescription
widgetitem