Skip to content
WANCRAFT

B.98Cards & Containers

Pan & Zoom Canvas

An infinite-feeling canvas: drag to pan with inertia, wheel or pinch to zoom around the cursor, with controls and keys.

NewReactMotionTailwindadvanced

Overview

Pan & Zoom Canvas is a viewport for whiteboards, diagrams and maps. Drag anywhere to pan (pointer capture, with an inertial glide on release), scroll or pinch to zoom around the cursor between a minimum and maximum, and use the toolbar for zoom out, a live zoom readout (click for 100%), zoom in, fit to content and reset. When focused, the arrow keys pan, + and − zoom, 0 resets and F fits. A dot grid scales and moves with the content, children are placed in canvas coordinates with CanvasItem, and an imperative handle exposes zoomTo, zoomIn, zoomOut, fitToContent, reset and getTransform. Transforms live in motion values, so panning and zooming never re-render your content.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/pan-zoom-canvas.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/pan-zoom-canvas.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/pan-zoom-canvas.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { PanZoomCanvas, CanvasItem } from "@/components/ui/pan-zoom-canvas";

export function PanZoomCanvasExample() {
  return (
    <div className="h-[480px] w-full rounded-xl border">
      <PanZoomCanvas>
        <CanvasItem x={0} y={0} className="w-48 rounded-lg bg-amber-100 p-4">Research</CanvasItem>
        <CanvasItem x={260} y={80} className="w-48 rounded-lg bg-emerald-100 p-4">Prototype</CanvasItem>
        <CanvasItem x={520} y={0} className="w-48 rounded-lg bg-sky-100 p-4">Ship</CanvasItem>
      </PanZoomCanvas>
    </div>
  );
}

03Props

PropTypeDefaultDescription
childrenReactNode—Content in canvas coordinates. Use <CanvasItem x y> or absolutely positioned elements.
minZoomnumber0.25Lowest zoom level.
maxZoomnumber3Highest zoom level.
zoomStepnumber1.25Multiplier used by the zoom buttons and the + / − keys.
wheelMode"zoom" | "pan""zoom"Wheel zooms around the cursor, or pans. Pinch and Ctrl / ⌘ + wheel always zoom.
inertiabooleantrueGlide after a drag is released (off with reduced motion).
showGridbooleantrueDot grid that pans and scales with the content.
gridSizenumber24Grid spacing at 100% zoom, in canvas px.
showControlsbooleantrueToolbar with zoom out, live readout (click for 100%), zoom in, fit and reset.
initialFitbooleantrueFit the content into view on mount and on reset; otherwise start at 100% at the origin.
fitPaddingnumber48Space kept around the content when fitting, in px.
accentstring"#FF7448"Focus ring colour.
labelstring"Canvas"Accessible name of the canvas region.
onTransformChange({ x, y, zoom }) => void—Called on every transform change, including animation frames.
refRef<PanZoomCanvasHandle>—{ zoomTo(zoom, origin?), zoomIn(), zoomOut(), fitToContent(), reset(), getTransform() }.
CanvasItemcomponent—Named export: <CanvasItem x y> absolutely places children at canvas coordinates.

04Features

  • 01Drag to pan with pointer capture and an inertial glide on release
  • 02Wheel and trackpad pinch zoom around the cursor; two-finger touch pinch via pointer events
  • 03Toolbar: zoom out, live zoom % (click for 100%), zoom in, fit to content, reset
  • 04Keyboard when focused: arrows pan (Shift for larger steps), + / − zoom, 0 resets, F fits
  • 05Dot grid scales and moves with the content
  • 06Imperative handle: zoomTo, zoomIn, zoomOut, fitToContent, reset, getTransform
  • 07Transforms live in motion values — panning and zooming never re-render children
motion · springinput · draginput · pointerinput · keyboardinput · click

05Accessibility

  • The viewport is a focusable region with a roledescription and a described keyboard hint.
  • All pan and zoom actions are available from the keyboard and from labelled toolbar buttons.
  • Discrete zoom changes (buttons, keys, fit, reset) are announced through a polite live region.
  • Buttons, links and inputs inside the canvas keep their own pointer and keyboard behaviour.
  • With prefers-reduced-motion, zoom and fit jump instantly and drags do not glide.

06Limitations

  • Wheel events over the canvas are captured (the page does not scroll there); use wheelMode="pan" or leave space around it on long pages.
  • Fit to content measures the canvas's direct children by their layout boxes; transformed children are measured untransformed.