useCanvas hook gives you a complete collaborative canvas surface: add and move shapes, broadcast viewports across peers, undo and redo, all synchronized over a CanvasRoom Durable Object. Use it for whiteboards, mood boards, diagram tools, and any “shapes on an infinite plane” UI.
Connect to a canvas
canvasId is any string. Two clients passing the same canvasId connect to the same CanvasRoom.
Use canWrite to disable draw / edit controls for viewers - shape mutations (addShape, moveShape, resizeShape, updateShape, deleteShape, undo, redo) silently no-op when canWrite is false, so unguarded buttons will appear to “click but do nothing” for read-only users. setViewport is exempt and stays open for viewers (viewport broadcasts are presence-like).
Shape lifecycle
A shape is an object with a position, size, and an arbitraryprops payload. Each shape has:
type is a free-form string - define your own vocabulary (rect, circle, arrow, text, image). The props field holds whatever metadata you need.
Mutation methods are all positional and return void:
Rendering shapes
shapes is a flat array of CanvasShapeClient records. Render them however you like - SVG, canvas, DOM:
shapes array re-renders. Note that addShape and deleteShape round-trip through the server, so the calling peer also gets the update from the broadcast. moveShape, resizeShape, and updateShape broadcast only to other peers - if you want the local peer to see the change immediately, update your UI optimistically.
Viewports
Each connected peer broadcasts a viewport (position, size, and zoom). Use it to show “where everyone is looking” in minimap-style UIs:Undo and redo
Each peer has an independent undo stack:Worker setup
The scaffold already wiresAppCanvasRoom:
A complete whiteboard
When to use canvas vs Yjs
Both can model “shapes that multiple users edit.” Pick based on the merging needs:| Use canvas when… | Use Yjs when… |
|---|---|
| Shapes are independent units | Shapes form a unified document |
| Last-write-wins per shape is fine | You need CRDT merging of overlapping edits |
| You need a built-in undo stack | You need fine-grained operation-log merging |
| Viewports / cursors are part of the surface | You want to use a third-party editor binding |
Next steps
- Collaborative editing - Yjs-backed text and structured documents.
- Presence and cursors - live cursors with
usePresenceRoom. - Real-time sync - how updates propagate.