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.

KQ-1289 · SNAP/BOARD

Requests

48.2k/min

Error rate

0.42%

p95 latency

184ms

Deploys

  • api 4.12.0
  • ingest 2.3.1
  • web 9.0.4
  • jobs 1.8.0

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).