Skip to content
MIZU.

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.

View .md

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.

What worked

Weekly demo kept design and engineering honest about scope.

Rin

Feature flags let support switch off the importer in one click.

Ade

“The staging data finally looked like production.”

QA review

What hurt

The migration plan lived in three documents and nobody owned it.

Tomás

Load tests ran against yesterday's schema.

Mei
Error budget, launch week

Next time

One owner per migration, named in the plan's first line.

Team

Rehearse the rollback, not just the rollout.

Team

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

tsx
"use client";

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>
  );
}

It starts with "use client" because it keeps state or passes functions to components, so it pastes into a Next.js page as it is. Other React setups ignore the directive.

This code, running — no configuration beyond the data

Research

Prototype

Ship it

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 into your configured UI directory, with base.css, Mizu’s tokens, and the stylesheets that hold its rules. Each rule lives in one stylesheet, so components you add together never repeat CSS. In a project without a components.json, run npx shadcn@latest init 0xuser64bit/mizu/preset first: plain init installs shadcn’s theme into your global CSS. Then load the fonts.

sh
npx shadcn@latest add 0xuser64bit/mizu/plane

Every component imports from its own path, its name in kebab case. To paste the example, replace its mizu-ui import with:

tsx
import { Plane, PlaneItem } from "@/components/ui/mizu/plane";

For 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. A Client Component: its source starts with “use client”.

Inspect signature/Plane.tsx

Source is included in the npm package under src/signature/Plane.tsx.

Open full source ↗

Props

PropTypeDefaultDescription
label / childrenstring / 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 / maxZoomnumber0.15 / 4Zoom limits.
grid / minimap / coordinates"dots" | "lines" | "none" / boolean / boolean"dots" / true / trueSurface texture and instruments.
toolsReactNode—Extra toolbar controls beside zoom and Fit.
refPlaneHandle—fit(), zoomBy(factor), flyTo(box | point) and toWorld(clientX, clientY).
PlaneItem: id / x / y / width / height / labelstring / 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.

Works alongside