[]previous and next page

React Native

A native canvas on iOS and Android with react-native-svg, multi-touch, pen pressure and native text input, sharing the same engine as the web.

canvasflow/react-native renders the same editor with react-native-svg. Documents, shapes, tools, actions, history and sync are the very same code as on the web, so a board drawn on a phone opens unchanged in a browser.

Install#

bash
npm install canvasflow react-native-svg

With Expo, prefer npx expo install react-native-svg so you get the version that matches your SDK.

A canvas#

App.tsxtsx
import { CanvasFlowView } from 'canvasflow/react-native'

export default function App() {
  return <CanvasFlowView style={{ flex: 1 }} onReady={(editor) => editor.setTool('draw')} />
}

CanvasFlowView takes every editor option plus:

Prop What it does
editor Use an editor you created (for example, to set up sync first)
themes { light, dark } themes
fonts The font family to use for each font token (hand, sans, serif, mono)
pointerType Treat touches as a pen, for stylus-first apps
onReady Called with the editor once it's mounted. Return a cleanup
children Your native UI, rendered above the canvas. Hooks work inside

Your own toolbar#

There's no default UI on native: you know your app's look better than any default could. The hooks are the same as on the web:

Toolbar.tsxtsx
import { Pressable, Text, View } from 'react-native'
import { useEditor, useValue } from 'canvasflow/react-native'

const TOOLS = ['select', 'draw', 'rectangle', 'arrow', 'eraser']

export function Toolbar() {
  const editor = useEditor()
  const tool = useValue((e) => e.state.toolId)
  return (
    <View style={{ position: 'absolute', bottom: 32, alignSelf: 'center', flexDirection: 'row', gap: 6 }}>
      {TOOLS.map((id) => (
        <Pressable key={id} onPress={() => editor.setTool(id)} style={{ padding: 12, borderRadius: 12, backgroundColor: tool === id ? '#4fe0c0' : '#222' }}>
          <Text style={{ color: tool === id ? '#04211b' : '#fff' }}>{id}</Text>
        </Pressable>
      ))}
    </View>
  )
}
App.tsxtsx
<CanvasFlowView style={{ flex: 1 }}>
  <Toolbar />
</CanvasFlowView>

Touch and pen#

  • One finger draws with the current tool. Two fingers pan and pinch to zoom.
  • A long press opens the context menu event (contextmenu), so you can show a native sheet.
  • Apple Pencil and other styluses report pressure, which shapes freehand strokes.
  • In pen mode (editor.updateState({ isPenMode: true })), fingers always pan and the pen always draws, so a resting palm never leaves a mark. It turns on by itself the first time a pen touches the screen.

Fonts#

React Native can't read CSS font stacks, so the view maps each font token to one installed family. Load your fonts with expo-font, then pass them in:

tsx
<CanvasFlowView style={{ flex: 1 }} fonts={{ hand: 'Virgil', sans: 'Inter', serif: 'Georgia', mono: 'Menlo' }} />

Saving and sync#

Everything from Saving and storage and Collaboration works on native. Use an AsyncStorage-backed adapter for storage, and the WebSocket transport (React Native has WebSocket built in):

storage.tsts
import AsyncStorage from '@react-native-async-storage/async-storage'
import type { StorageAdapter } from 'canvasflow'

export const asyncStorage: StorageAdapter = {
  get: (key) => AsyncStorage.getItem(key),
  set: (key, value) => AsyncStorage.setItem(key, value),
  remove: (key) => AsyncStorage.removeItem(key),
}

Sharing a web UI instead#

If you'd rather reuse a web interface on mobile (with Expo DOM components, say), render the web build inside it. CanvasFlow works the same there. Use the native view when you want native gestures and native performance; use the web build when you want one UI everywhere.