SIG—04 Signature system
Plane
An infinite, zoomable surface for boards, maps and diagrams — inertia, pinch, a minimap and a camera that follows keyboard focus.
Live — this one is real
Drag cards to rearrange. Drag the background to travel, pinch or Ctrl-scroll to zoom, tab between cards and the view follows. Illustrative content.
Arrow keys move the view, plus and minus zoom, 0 fits everything, 1 returns to 100%. Tab moves between items and the view follows.
Usage
import { useState } from "react";
import { Plane, PlaneItem } from "mizu-ui";
export function Example() {
const [notes, setNotes] = useState([
{ id: "a", x: 0, y: 0, text: "Research" },
{ id: "b", x: 260, y: 90, text: "Prototype" },
{ id: "c", x: 90, y: 260, text: "Ship it" },
]);
return (
<Plane label="Planning board">
{notes.map((n) => (
<PlaneItem
key={n.id}
id={n.id}
x={n.x}
y={n.y}
width={200}
label={n.text}
onMove={(x, y) =>
setNotes((all) =>
all.map((a) => (a.id === n.id ? { ...a, x, y } : a)),
)
}
>
<p
style={{
margin: 0,
padding: 18,
background: "var(--mizu-ink-2)",
border: "1px solid var(--mizu-line-bright)",
}}
>
{n.text}
</p>
</PlaneItem>
))}
</Plane>
);
}
This code, running — no configuration beyond the data
Arrow keys move the view, plus and minus zoom, 0 fits everything, 1 returns to 100%. Tab moves between items and the view follows.
Keyboard
- ←→↑↓
- Travel (Shift for larger steps)
- +−
- Zoom
- 0·1
- Fit everything · 100%
- Tab
- Next item — the view follows
- ←→↑↓
- On a draggable item: nudge it (Shift: 48px)
- Ctrl+scroll
- Zoom at the pointer; double-click zooms in
Source & setup
Add this component to your project with the shadcn CLI. The command copies its source and styles into your configured UI directory.
npx shadcn@latest add 0xuser64bit/mizu/planeFor npm imports, import mizu-ui/styles.css once. Fonts are optional via mizu-ui/fonts.css. Override --mizu-* tokens or use className for local styling. Requires React and Motion.
Inspect signature/Plane.tsx
Source is included in the npm package under src/signature/Plane.tsx.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label / children | string / ReactNode | — | Accessible name and world content (usually PlaneItems). |
| view / defaultView / onViewChange | { x, y, zoom } | — | Camera: the world point at the centre and the zoom. Frames the content by default. |
| minZoom / maxZoom | number | 0.15 / 4 | Zoom limits. |
| grid / minimap / coordinates | "dots" | "lines" | "none" / boolean / boolean | "dots" / true / true | Surface texture and instruments. |
| tools | ReactNode | — | Extra toolbar controls beside zoom and Fit. |
| ref | PlaneHandle | — | fit(), zoomBy(factor), flyTo(box | point) and toWorld(clientX, clientY). |
| PlaneItem: id / x / y / width / height / label | string / number | — | World position and size; measured when width or height is omitted. |
| PlaneItem: onMove / onMoveEnd | (x, y) => void | — | Makes the item draggable and nudgeable. |
Accessibility
The viewport is a focusable canvas group with keyboard travel and zoom. Items are named groups in reading order; tabbing to one flies the camera to it. Draggable items nudge with arrow keys. Plain scrolling only pans once the canvas is active, so the page never traps the wheel; a hint explains Ctrl-scroll.
Motion
The camera glides in log space and pulls back on long flights before settling in; drags coast to rest with inertia. The zoom-adaptive dot grid re-densifies as you scale. Reduced motion jumps directly and removes coasting.