A whiteboard for React Native: drawing, Apple Pencil, and palm rejection
Search for “React Native whiteboard” and the results are thin: abandoned sketch-canvas wrappers, SVG-based signature pads that choke past a hundred strokes, and advice to “just use a WebView” with no details. Meanwhile the two best-known web whiteboard SDKs don’t ship React Native packages at all.
Here’s the approach that actually works — and a package that does it for you.
Why native-view drawing keeps disappointing
Naive RN drawing implementations render strokes as SVG paths or per-point views on the JS thread. Every pointer move crosses the bridge, every stroke re-renders a component tree, and by the time you add zoom, selection, and undo, you’ve rebuilt a graphics engine inside a UI framework that fights you. The libraries that tried are mostly unmaintained for a reason.
The pragmatic architecture is a WebView running a real canvas engine, with
a typed message bridge to the host app. Canvas 2D inside a WebView is
hardware-accelerated, handles thousands of strokes, and — crucially — gets
PointerEvent with pressure from Apple Pencil and S Pen.
The five problems you’ll hit doing it yourself
- Asset loading. Shipping an HTML file that works in release builds on both platforms (and Expo) is fiddly; remote URLs break offline.
- Bridge protocol. You need request/response semantics over
postMessage— for “give me a snapshot” and “export a PNG” — not just fire-and-forget events. - Pressure & palm rejection. Pencil input arrives as pointer events with
pointerType: "pen"; a good engine lets the palm’s touches pan the camera while the pen draws. - Gesture conflicts. Pinch-zoom inside the WebView vs. the scroll views around it.
- Binary export. Getting a PNG out means base64 across the bridge — easy to get slow, easy to get wrong.
Or: install the package that solved them
@quickdrawjs/react-native
wraps the MIT-licensed Quickdraw engine in exactly
this architecture. The engine ships inside the package as one
self-contained HTML string — no network, works offline, no asset pipeline.
npm install @quickdrawjs/react-native react-native-webview
import { useRef } from 'react'
import { Quickdraw } from '@quickdrawjs/react-native'
export default function Board() {
const board = useRef(null)
return (
<Quickdraw
ref={board}
theme="dark"
onChange={(diff) => console.log('changed', diff)}
style={{ flex: 1 }}
/>
)
}
You get the full whiteboard — pressure ink, highlighter, shapes, sticky notes, selection, infinite canvas, undo, themes — with Apple Pencil pressure and palm rejection handled by the engine.
The bridge is promise-based and typed:
const snapshot = await board.current.getSnapshot() // plain JSON
await board.current.loadSnapshot(saved)
const png = await board.current.exportPng({ scale: 2 })
onChange streams the same JSON diffs the web packages emit, so an RN client
and a web client can sync to each other over one relay.
Expo notes
It’s a plain react-native-webview consumer — no native modules of its own —
so it works in Expo Go and EAS builds without config plugins.
When you shouldn’t use a WebView
If you need strokes composited with native UI (drawing over a live camera
feed, for instance) or sub-frame latency guarantees, a Skia-based custom
renderer (@shopify/react-native-skia) is the right tool — at the cost of
building the whiteboard layer yourself. For a document-style whiteboard,
notes app, or annotation feature, the WebView architecture is what ships.
Quickdraw is MIT-licensed and open to contributions.