Building your own UI
Skip the default interface and drive the canvas from your own toolbar, panels and menus. Everything you need comes from the editor.
Without canvasflow/ui, you still get a complete canvas: rendering, selection handles, snapping guides, text editing, the clipboard, drag and drop, keyboard shortcuts. What's missing is chrome, and the editor gives you everything you need to build your own.
A live CanvasFlow editor runs here when JavaScript is on.
The pieces of a toolbar#
Every control in a whiteboard interface comes down to reading some state and calling a method:
| Control | Read | Change |
|---|---|---|
| Tool buttons | editor.state.toolId |
editor.setTool(id) |
| Tool lock | editor.state.toolLocked |
editor.toggleToolLock() |
| Undo and redo | editor.canUndo, editor.canRedo |
editor.undo(), editor.redo() |
| Style pickers | editor.getSharedStyles() |
editor.setStyle(key, value) |
| Opacity | editor.getSharedOpacity() |
editor.setOpacity(value) |
| Zoom | editor.camera.z |
editor.zoomIn(), zoomOut(), resetZoom(), zoomToFit() |
| Any command | editor.actions.isEnabled(id) |
editor.actions.run(id) |
In plain JavaScript#
import { createCanvasFlow } from 'canvasflow/dom'
const app = createCanvasFlow(document.getElementById('board')!)
const { editor } = app
const toolbar = document.getElementById('toolbar')!
for (const id of ['select', 'draw', 'rectangle', 'arrow', 'text']) {
const button = document.createElement('button')
button.textContent = id
button.onclick = () => editor.setTool(id)
toolbar.append(button)
}
// Keep the pressed state in sync, however the tool changes (click, keyboard, code).
editor.on('tool', ({ id }) => {
for (const b of toolbar.querySelectorAll('button')) b.setAttribute('aria-pressed', String(b.textContent === id))
})
In a framework#
Every binding has a headless canvas and a way to follow the editor, so the recipe is the same everywhere: mount the headless canvas, read state with useValue, call editor methods from your buttons. Each framework's page has a toolbar written in its own style:
- React and Next.js, Preact and Solid
- Vue, Svelte and Angular
- ShadowJS, which is what the demo above is written in
The pieces below are plain editor calls, so they carry over unchanged. In React:
A style panel follows the same pattern:
import { COLOR_TOKENS } from 'canvasflow'
import { useEditor, useValue } from 'canvasflow/react'
export function ColorPicker() {
const editor = useEditor()
const color = useValue((e) => e.getSharedStyles().color)
if (!color) return null // nothing selected supports a color
return (
<div role="radiogroup" aria-label="Color">
{COLOR_TOKENS.map((c) => (
<button
key={c}
role="radio"
aria-checked={!color.mixed && color.value === c}
style={{ background: `var(--cf-c-${c})` }}
onClick={() => editor.setStyle('color', c)}
/>
))}
</div>
)
}
The --cf-c-* variables come from the canvas theme, so your swatches match the canvas in light and dark.
Menus and context menus#
The editor tells you when a context menu should open, and with what under the pointer:
editor.on('contextmenu', ({ point, shapeId }) => {
openMyMenu(point, shapeId ? editor.getSelectedIds() : [])
})
Fill it with actions, so labels, shortcuts and enabled states come for free:
const items = ['copy', 'duplicate', 'group', 'bring-to-front', 'delete'].map((id) => ({
label: editor.actions.get(id)!.label,
shortcut: editor.actions.shortcutText(id),
disabled: !editor.actions.isEnabled(id),
run: () => editor.actions.run(id, 'context-menu'),
}))
Files, export and the clipboard#
The app object handles the browser side:
await app.downloadPng({ scale: 2, background: true })
app.downloadSvg()
await app.copyAsPng()
await app.saveFile() // .canvasflow
await app.openFile()
await app.insertImages() // opens the file picker
Toasts and feedback#
Listen for toast to show the editor's messages in your own style. When undoable is true, offer an Undo button that calls editor.undo().
What you don't have to build#
The DOM host already gives you, without any UI:
- selection outlines, resize and rotate handles, point handles,
- snapping guides and the marquee,
- in-place text editing with IME support,
- every keyboard shortcut,
- cursors, including rotated resize cursors,
- drag and drop of images and files, and paste from anywhere.