Choose your framework
CanvasFlow runs in any framework. Each one has a small binding with the same pieces, and anything else can use the web host or the custom element.
CanvasFlow is not tied to a framework. The editor, the renderer and the default UI are plain TypeScript and DOM. A binding is a thin layer that mounts all of that for you and hands the editor's state to your framework in its own way: hooks in React, refs in Vue, signals in Solid and Angular, stores in Svelte.
Every binding has the same four pieces, so what you learn in one carries over to the next:
| Piece | What it is |
|---|---|
| A canvas | <CanvasFlow /> (or an action, or mountCanvasFlow) mounts the canvas and the default UI. |
| A headless canvas | <CanvasFlowCanvas /> mounts only the canvas. It never references the default UI, so bundlers drop the toolbar, the panels and their CSS. Build your own chrome. |
| The editor | useEditor() gives you the editor inside a canvas. Everything in The editor works. |
| Derived state | useValue(selector) follows any value of the editor and updates only when it changes. useSelectedIds, useTool, useCamera, useCanUndo, useCanRedo, useTheme and useDirection are ready-made. |
Pick yours#
| Framework | Import | State comes as | Peer |
|---|---|---|---|
| React and Next.js | canvasflow/react |
Hooks | react, react-dom |
| Vue | canvasflow/vue |
Refs | vue 3.3 or newer |
| Solid | canvasflow/solid |
Signals | solid-js 1.8 or newer |
| Svelte | canvasflow/svelte |
Stores | none |
| Angular | canvasflow/angular |
Signals | @angular/core 17 or newer |
| Preact | canvasflow/preact |
Hooks | preact 10 |
| ShadowJS | canvasflow/shadow |
Signals | @shadow-js/core |
| React Native | canvasflow/react-native |
Hooks | react-native, react-native-svg |
| Web component | canvasflow/dom |
Events and properties | none |
| Plain JavaScript | canvasflow/dom, canvasflow/ui |
The editor's own API | none |
Peers are optional and only matter for the entry you import. A Vue app never installs React, and the other way round.
What is the same everywhere#
Options are read once. The canvas mounts with the options you give it, the way a ref works. To start over with different options, remount it (in React and Preact that means changing key). Two options follow later changes: dir and readonly.
You get the editor when it is ready. Every binding has a way to receive the editor once the canvas has mounted (onMount, a mount event, the returned ref). Return a function from it and it runs as a cleanup when the canvas goes away.
Server rendering is safe. Importing a binding on a server touches no browser globals. The canvas itself is client-only: the server renders an empty box, and the canvas mounts in the browser.
Headless is its own component. <CanvasFlow ui={false} /> works, but it still references the default UI, so a bundler keeps it. <CanvasFlowCanvas /> does not, and the bundle size checks prove it for every binding.
A framework that is not listed#
Use the web host directly. Three calls give you a complete editor in any environment that has a DOM:
import { createCanvasFlow } from 'canvasflow/dom'
import { mountUI } from 'canvasflow/ui'
const app = createCanvasFlow(document.getElementById('board')!)
const ui = mountUI(app)
// Later, when your framework unmounts the element:
ui.destroy()
app.destroy()
app.editor is the editor, and editor.subscribe(fn) tells you when anything changed. That is all a binding does, with the framework's lifecycle around it. The web component wraps the same thing as a custom element, which every framework can render.