Skip to content

Whiteboard

Sketch, write sticky notes, connect shapes, embed your own components, pin comments and see who else is on the board, then export the scene.

import { Whiteboard, WhiteboardCursor, WhiteboardHandle } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use a Whiteboard for diagrams and planning inside a product: a flow in a ticket, a retro, a review with comments, a canvas of live metrics. For laid-out graphs that come from data, a Flow Graph fits better.

import { useState } from 'react'
import { Whiteboard, type WhiteboardScene } from '@youj/react'

export function Board({ chart }: { chart: React.ReactNode }) {
  const [scene, setScene] = useState<WhiteboardScene>({ shapes: [] })
  return <Whiteboard value={scene} onValueChange={setScene} embeds={{ revenue: chart }} height={520} />
}

A shape is { id, type, color, width, … }: pen keeps its points ([x, y, pressure]), line and arrow their ends, rectangle and ellipse a box, text a position and its text, sticky a box (its height the least it takes; it grows to fit) and its text, connector its from and to ({ shape } or { x, y }), route and label, and embed a box and a title. color names a theme role (ink, accent, red, amber, green, blue), so the board follows light, dark and every preset; a note's paper is its role's soft fill. A pin is { id, x, y, shape?, resolved?, author?, text? }. Scenes saved before notes, connectors, embeds and pins load as they are.

Reference

Propsvalue, tools, embeds, pinContent, cursors…

Props

Whiteboard props
PropType
valueWhiteboardScene–
defaultValueWhiteboardScene

default EMPTY_SCENE

EMPTY_SCENE
onValueChange(scene: WhiteboardScene) => void–
defaultToolWhiteboardTool
'select''hand''pen''line''arrow''rectangle''ellipse''text''sticky''connector''comment'

The tool the board opens with. Default pen, or select when it opens with shapes.

–
toolsreadonly WhiteboardTool[]

The tools the toolbar offers, in its order.

default ALL_TOOLS

ALL_TOOLS
readOnlyboolean

Shows the scene with the view controls only.

default false

false
heightnumber

The board's height in pixels.

default 480

480
labelstring

The board's accessible name. Default: the labels' "Whiteboard".

–
embedsRecord<string, ReactNode> | ((embed: WhiteboardEmbed) => ReactNode)

What each embed shape holds, by its id: a node, or a function of the shape.

–
pinContent(pin: WhiteboardPin, actions: ReactNode) => ReactNode

What a comment pin opens: your thread (CommentThread, say). It gets the pin's Resolve and Delete buttons as actions, to place in its own header (CommentThread's actions), so the popover has one header. Default: the pin's name, its actions and a note field that writes pin.text.

–
cursorsreadonly WhiteboardCursor[]

Other people's pointers, in board coordinates.

–
onCursorMove(point: { x: number; y: number } | null) => void

Your pointer in board coordinates as it moves (throttled, about twenty times a second), and null when it leaves: send it to the others.

–
defaultMinimapboolean

Show the minimap at first.

default false

false
defaultSnapToGridboolean

Snap moves and new shapes to the grid at first.

default false

false
refRef<WhiteboardHandle>

toSVG, toPNG, zoomToFit and zoomToSelection.

–
Other props pass through: extends ComponentProps<'div'>.

Handle

MethodDoes
toSVG()The scene as a standalone SVG, cropped to the shapes, in the theme's colors; embeds as titled placeholders
toPNG({ scale })The same picture as a PNG Blob, scale times its size (default 2), on the board's surface color
zoomToFit()Scales and centers the view on the whole scene
zoomToSelection()Scales and centers the view on the selection

Keyboard shortcuts

KeysDoes
V, H, P, L, A, R, O, T, S, X, CSelect, Hand, Pen, Line, Arrow, Rectangle, Ellipse, Text, Sticky note, Connector, Comment
⌘Z / Ctrl+Z, ⇧⌘Z / Ctrl+YUndo, redo
Delete or BackspaceDelete the selection (and its connectors)
⌘A / Ctrl+ASelect every shape
Arrow keysNudge the selection (Shift: ten times as far); on the board, pan
⌥ / Alt + A, H, D, W, V, SAlign left, center, right, top, middle, bottom
⌥⇧ / Alt+Shift + H, VDistribute horizontally, vertically
Space + dragPan
+ − 0Zoom in, zoom out, back to 100%
⇧1, ⇧2Fit the board, zoom to the selection
MShow or hide the minimap
?List these shortcuts
Enter (on the board)Place the tool's shape in the middle of the view; with Connector, join the two selected shapes; with Comment, pin the selected shape
Enter (on a shape)Edit a text, a note or a connector's label

Data attributes

The root carries data-yj-tool (the active tool) and data-readonly; each shape data-yj-shape (its type) and data-selected when selected; a pin data-yj-resolved once resolved.

MotionView easing · reduced motion

Motion

Motion tokens
MomentTokens
Drawing and draggingfollow the pointer sample by sample, never eased
no token

Wheel, pinch and the zoom buttonsmove the view at once
no token

Fit and zoom to selectionease the view there on the base token
no token

Remote cursorsLive Cursors’ spring, between sparse updates
no token

Reduced motion

Fitting the view jumps; remote cursors jump to each update.

Reduced motion

The board reads and works the same; nothing eases.

AccessibilityKeyboard, focus, announcements

Accessibility

  • The toolbar and the zoom bar are each one tab stop with arrow-key roving; each tool is a toggle named by its label, with its key in aria-keyshortcuts and in its tooltip.
  • The board is a named group described by how many shapes it holds and how to work it from the keyboard. Its shapes are a multi-select listbox and one tab stop: Tab walks them (bringing an offscreen one into view) and leaves the board after the last. Each is named for what it is: "Sticky note: Ship it", "Connector: Sign up to Verify email", "Embed: Revenue".
  • A focused shape is selected; the arrow keys nudge it, Delete removes it, Enter edits its words and Escape lets go. Align and distribute have keys, so nothing needs a pointer; with a drawing tool, Enter on the board places that shape.
  • An embed's content is a region named by its title, outside the list of shapes, so its own controls keep their roles.
  • Pins are a named group of buttons ("Comment 2 by Ana Ruiz, Resolved"); each opens its thread in a popover that takes focus and returns it.
  • Additions, deletions and selection changes are announced in a polite live region; Live Cursors announces who joins and leaves.