# DashboardWidgetSpec

Import it from `@grafloria/element`.

A widget, declared as data.

```ts
interface DashboardWidgetSpec
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` |  |  |
| `kind?` | `string` |  | Free-form kind string handed back to `renderWidget` (e.g. 'kpi', 'line'). |
| `span?` | `number` |  | Column span (default 3) and row span (default 1). |
| `rows?` | `number` |  |  |
| `x?` | `number` |  | Explicit cell. Omit and widgets flow in declaration order, wrapping at the column count — the common case needs no coordinates at all. |
| `y?` | `number` |  |  |
| `pinned?` | `boolean` |  | Pinned: never pushed, refuses the mover, survives every reflow. |
| `limits?` | `{ minSpan?: number; maxSpan?: number; minRows?: number; maxRows?: number }` |  | SIZE LIMITS in cells (gridstack's minW/maxW/minH/maxH). A resize — by hand, by the API, or by a column change scaling widths — clamps to them. `maxRows` here is the WIDGET's row limit; a container's inner row count is its own `maxRows` field one level up, which is why these live in `limits`. |
| `movable?` | `boolean` |  | May the user drag it? Default true. The API can always move it. |
| `resizable?` | `boolean` |  | May the user resize it? Default true (no handle when false). The API can always resize it. |
| `data?` | `Record<string, unknown>` |  | Your payload — passed straight back to `renderWidget`. |
| `title?` | `string` |  | Optional title used by the built-in fallback renderer. |
| `widgets?` | `DashboardWidgetSpec[]` |  | CONTAINMENT. A widget carrying `widgets` is a CONTAINER: it mounts as a member group (a locked slab in its parent's grid, exactly like a view's board one level down) with its own nested pack grid bound on it. Children lay out inside its frame; dragging a tile across the boundary adopts it live in either direction, and one undo restores the whole gesture. |
| `columns?` | `number` |  | Container only: column count of the INNER grid (default: the parent board's column count). |
| `maxRows?` | `number` |  | Container only: the inner grid's designed row count. A child resized past it ESCALATES — the container's slab grows a row in the parent board (the ratchet), instead of the child overflowing the frame. Default: the row extent of the declared children. |
| `layout?` | `'grid' \| 'split' \| 'tabs'` |  | Container only: the inner board's layout, exactly as a view's. `'grid'` (default) packs the children in cells; `'split'` is a splitter tree that always covers the pane. Switch live with `setLayout(mode, containerId)`; `toJSON()` writes it per container. |
| `active?` | `string` |  | TAB CONTAINER (`layout: 'tabs'`). Every child that carries `widgets` is a PAGE: one is visible at a time, and a strip of tabs across the top switches between them (DevExpress' Tab Container; VS Code's editor area is a split of these). `active` is the page showing, persisted by `toJSON()`; `tabs` styles the strip. |
| `tabs?` | `TabsOptions` |  |  |
| `caption?` | `SectionCaption` |  | Container only: a CAPTION painted by the kit on the section's slab — `true` for the title, a string, or the full options (subtitle, description, icon, position, alignment, typography, box, show, actions, pass-through, className). Reserved inside the frame, selectable, themed, persisted by `toJSON()`; live through `setCaption()`. Default: none. |
| `tree?` | `SplitNode \| null` |  | Container only, split layout: the authored splitter tree. Omit it and the tree is derived from the children's cells. `toJSON()` writes it back. |
| `sizing?` | `'fit' \| 'grow'` |  | Container only: what a pull past the pane's rows does. `'grow'` (default): the container's slab grows a row in the parent — the ratchet above. `'fit'`: the pane is the bound — a child that needs a row the pane does not hold is refused where it stands, and nothing outside the pane moves. |
