KQ-1289 · SNAP/BOARD
Snap Board
A dashboard layout editor on a grid of columns and rows: a widget dragged by its header lifts on the flick spring and follows the finger 1:1, rubber-banding past the board's edges, and each time the cell under it changes a detent clicks, the ghost moves to the landing cell on snap and every widget in the way glides aside on glide. Let go, it flies to the ghost with the throw's own velocity and lands with a thock, and the layout is reported once; the corner resizes the same way, and push decides whether the others float back up, stay where they land or trade places. Every grip is a button: Space or Enter picks a widget up, arrows move it a cell (the widget snapping along with its ghost), Shift and arrows resize it, Enter drops and Escape puts everything back.
Requests
Up 6% on last week
Error rate
Budget 1% · 58% left
p95 latency
Slower since 14:05
Deploys
- api 4.12.012 min ago
- ingest 2.3.11 h ago
- web 9.0.4rolled back
- jobs 1.8.0yesterday
Regions
- North41%
- Coast27%
- Basin19%
- Hills13%
Uptime, 30 days
99.96% · 2 incidents, 41 min down
Press Space or Enter on a widget's grip to pick it up. Arrow keys move it a cell, Shift and arrow keys resize it, Enter drops it and Escape puts it back.
6 widgets · 12 columns · 7 rows
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/snap-board
Props
- widgets
- SnapBoardWidget[]
- default defaultBoardWidgets
- The widgets on the board: id, title, icon, content, and min and max size in cells. Content sits in a container-query box so it can lay itself out for the size it is given.
- layout / defaultLayout
- SnapBoardItem[]
- default defaultBoardLayout(columns)
- One item per widget: id, x and y of its top-left cell, w and h in cells. Anything wider than the columns or overlapping is clamped and settled.
- onLayoutChange
- (layout: SnapBoardItem[]) => void
- default —
- Fires from the drop, resize or key that changed the layout, with all of it. A drop that changes nothing is not reported.
- onMove
- (id: string, moved: string[]) => void
- default —
- A drop that changed the layout: the widget dropped and the ids of the others that made way.
- onDragChange
- (drag: SnapBoardDrag | null) => void
- default —
- A widget picked up by pointer or key, its landing cell (id, mode and item) each time it changes, and null when it is put down.
- columns
- number
- default 12
- The grid's columns. Column guides fade in under the widgets while one is held.
- push
- "compact" | "free" | "swap"
- default "compact"
- How the others make way: pushed and floated back up into any gap, pushed and left where they land, or the widget under the drop trades places with the dragged one.
- ghost
- "outline" | "tint" | "none"
- default "outline"
- The landing preview: a dashed accent outline, an accent wash with a solid edge, or nothing but the others making way.
- rowHeight
- number
- default 44
- One row's height, px.
- gap
- number
- default 8
- The space between cells, px.
- maxHeight
- number | string
- default —
- The tallest the board gets before it scrolls inside itself, px or any CSS length.
- editable
- boolean
- default true
- Off: no grips and no corners, a plain dashboard.
- label
- string
- default "Dashboard layout"
- The board's accessible name.
- moveLabel
- (title: string) => string
- default (title) => `Move ${title}`
- Each grip's accessible name.
- resizeLabel
- (title: string) => string
- default (title) => `Resize ${title}`
- Each corner's accessible name.
- accent
- string
- default "var(--accent-bright)"
- The ghost, the picked-up ring, the column guides and the default widgets' charts. Any CSS colour.
- sound
- boolean
- default false
- A detent click at every cell the target crosses and a thock on every drop (louder for a longer flight). Nothing sounds when the host changes the layout.
- disabled
- boolean
- default false
- Dims the board and stops every drag, resize and key.
- className
- string
- default —
- Merged onto the root (the labelled region that clips the board).
| Prop | Type | Default | Description |
|---|---|---|---|
| widgets | SnapBoardWidget[] | defaultBoardWidgets | The widgets on the board: id, title, icon, content, and min and max size in cells. Content sits in a container-query box so it can lay itself out for the size it is given. |
| layout / defaultLayout | SnapBoardItem[] | defaultBoardLayout(columns) | One item per widget: id, x and y of its top-left cell, w and h in cells. Anything wider than the columns or overlapping is clamped and settled. |
| onLayoutChange | (layout: SnapBoardItem[]) => void | — | Fires from the drop, resize or key that changed the layout, with all of it. A drop that changes nothing is not reported. |
| onMove | (id: string, moved: string[]) => void | — | A drop that changed the layout: the widget dropped and the ids of the others that made way. |
| onDragChange | (drag: SnapBoardDrag | null) => void | — | A widget picked up by pointer or key, its landing cell (id, mode and item) each time it changes, and null when it is put down. |
| columns | number | 12 | The grid's columns. Column guides fade in under the widgets while one is held. |
| push | "compact" | "free" | "swap" | "compact" | How the others make way: pushed and floated back up into any gap, pushed and left where they land, or the widget under the drop trades places with the dragged one. |
| ghost | "outline" | "tint" | "none" | "outline" | The landing preview: a dashed accent outline, an accent wash with a solid edge, or nothing but the others making way. |
| rowHeight | number | 44 | One row's height, px. |
| gap | number | 8 | The space between cells, px. |
| maxHeight | number | string | — | The tallest the board gets before it scrolls inside itself, px or any CSS length. |
| editable | boolean | true | Off: no grips and no corners, a plain dashboard. |
| label | string | "Dashboard layout" | The board's accessible name. |
| moveLabel | (title: string) => string | (title) => `Move ${title}` | Each grip's accessible name. |
| resizeLabel | (title: string) => string | (title) => `Resize ${title}` | Each corner's accessible name. |
| accent | string | "var(--accent-bright)" | The ghost, the picked-up ring, the column guides and the default widgets' charts. Any CSS colour. |
| sound | boolean | false | A detent click at every cell the target crosses and a thock on every drop (louder for a longer flight). Nothing sounds when the host changes the layout. |
| disabled | boolean | false | Dims the board and stops every drag, resize and key. |
| className | string | — | Merged onto the root (the labelled region that clips the board). |