Quick start
Put a working whiteboard on screen in a few minutes, in the framework you already use.
Pick your setup. Every tab ends with the same thing: a full editor with tools, styles, undo, export and autosave.
<div id="board" style="height: 100vh"></div>
<script type="module" src="./main.ts"></script>
import { createCanvasFlow } from 'canvasflow/dom'
import { mountUI } from 'canvasflow/ui'
const app = createCanvasFlow(document.getElementById('board')!, {
persistenceKey: 'my-board', // autosave to IndexedDB under this key
})
mountUI(app)
createCanvasFlow mounts the canvas: rendering, input, text editing, clipboard and drag and drop. mountUI adds the default interface on top. Leave it out and you have a blank, fully working canvas for your own UI.
import { CanvasFlow } from 'canvasflow/react'
export function Board() {
return <CanvasFlow persistenceKey="my-board" style={{ height: '100vh' }} />
}
<CanvasFlow/> mounts the canvas and the default UI. Children render inside it, so hooks like useEditor() work in your own panels:
import { useValue } from 'canvasflow/react'
export function SelectionCount() {
const count = useValue((editor) => editor.getSelectedIds().length)
return <div className="badge">{count} selected</div>
}
'use client'
import { CanvasFlow } from 'canvasflow/react'
export default function BoardPage() {
return <CanvasFlow persistenceKey="my-board" style={{ height: '100dvh' }} />
}
The component is a client component. On the server it renders an empty box of the right size, so there's no layout shift when the canvas mounts. No dynamic(..., { ssr: false }) needed.
<script setup lang="ts">
import { CanvasFlow } from 'canvasflow/vue'
</script>
<template>
<CanvasFlow persistence-key="my-board" style="height: 100vh" />
</template>
<CanvasFlow> mounts the canvas and the default UI. Children render inside it, so useEditor() and useValue() work in your own panels. See Vue.
import { CanvasFlow } from 'canvasflow/solid'
export function Board() {
return <CanvasFlow persistenceKey="my-board" style={{ height: '100vh' }} />
}
The primitives (useEditor, useValue…) return signals. See Solid.
<script>
import { createFlow } from 'canvasflow/svelte'
const flow = createFlow()
</script>
<div use:flow.action={{ persistenceKey: 'my-board' }} style="height: 100vh"></div>
An action mounts the canvas into any element, and flow.value(selector) gives you a store of anything in the editor. See Svelte.
@Component({ selector: 'app-board', template: '<div #host style="height: 100vh"></div>' })
export class Board implements AfterViewInit, OnDestroy {
@ViewChild('host') host!: ElementRef<HTMLElement>
flow!: CanvasFlowRef
ngAfterViewInit() {
this.flow = mountCanvasFlow(this.host.nativeElement, { persistenceKey: 'my-board' })
}
ngOnDestroy() {
this.flow.destroy()
}
}
mountCanvasFlow returns a ref whose state (flow.tool(), flow.selectedIds()…) is Angular signals. See Angular.
import { CanvasFlow } from 'canvasflow/preact'
export function Board() {
return <CanvasFlow persistenceKey="my-board" style={{ height: '100vh' }} />
}
The same components and hooks as React, on Preact's own hooks, no preact/compat. See Preact.
import { CanvasFlow } from 'canvasflow/shadow'
export function Board() {
return <CanvasFlow persistenceKey="my-board" style="height: 100vh" />
}
The component returns real DOM, and the state comes as ShadowJS signals. See ShadowJS.
import { CanvasFlowView } from 'canvasflow/react-native'
export default function App() {
return <CanvasFlowView style={{ flex: 1 }} onReady={(editor) => editor.setTool('draw')} />
}
Install the peer first: npx expo install react-native-svg. The view handles multi-touch, pen pressure and native text input. Your own toolbar goes in as children. See React Native.
<canvas-flow id="board" persistence-key="notes" theme="dark" style="height: 100vh"></canvas-flow>
<script type="module">
import { defineCanvasFlowElement } from 'canvasflow/dom'
import { mountUI } from 'canvasflow/ui'
defineCanvasFlowElement()
document.getElementById('board').extensions = [(app) => { const ui = mountUI(app); return () => ui.destroy() }]
</script>
A custom element for pages without a framework, or frameworks that like web components (Astro, Lit). See Web component.
Do something with it#
The editor is the one object you talk to. Every interface gives it to you: app.editor in plain JS, onMount or useEditor() in the framework bindings, onReady in React Native.
const id = editor.createShape({
type: 'geo',
x: 100,
y: 100,
props: { geo: 'rectangle', w: 220, h: 120, text: 'Hello', color: 'blue', fill: 'semi' },
})
editor.select(id)
editor.zoomToSelection({ animate: true })
editor.on('change', ({ source }) => {
if (source === 'user') console.log('Someone edited the board')
})
That's the shape of everything in CanvasFlow: create and update records, listen for changes, and let the editor take care of undo, rendering and sync.
Where to go next#
- How it fits together: The five layers, in two minutes.
- The editor: State, camera, selection and undo.
- Saving and storage: Autosave, your own backend, images.
- Collaboration: Live editing with others.