tldraw/tldraw: Infinite Canvas SDK for React
Complete setup guide for tldraw - an open-source whiteboarding and diagramming SDK that provides a feature-complete infinite canvas engine with real-time collaboration, drawing/diagramming tools, and a powerful runtime API. Includes installation, architecture overview, multiplayer sync setup with @tldraw/sync and Cloudflare Durable Objects, custom shapes/tools creation, and deployment patterns for production applications.
- Step 1
Overview: What is tldraw?
tldraw is an open-source whiteboarding and diagramming SDK that provides a feature-complete infinite canvas engine for building custom canvas applications in React. With over 47,000 stars on GitHub, it's widely adopted for collaborative whiteboarding, diagramming tools, and custom canvas-based applications.
Key Features: • Multiplayer collaboration with @tldraw/sync • Infinite canvas with DOM-based rendering • Pressure-sensitive drawing and geometric shapes • Arrows, snapping, rich text support • Image and video embedding • Custom shapes, tools, and bindings • Runtime API for programmatic control • AI integrations for LLM-powered canvas apps • Cross-platform: desktop, tablet, mobile • Fully extensible architecture - Step 2
Architecture Overview
tldraw is built as a monorepo with specialized packages for different aspects of the SDK. Understanding this architecture helps you choose the right packages for your use case.
Core Packages: ├── @tldraw/editor - Core editor engine (shapes, tools, interactions) ├── @tldraw/store - State management and data persistence ├── @tldraw/state - Application state handling ├── @tldraw/state-react - React bindings for state ├── @tldraw/tlschema - Type-safe data schema ├── @tldraw/utils - Utility functions ├── @tldraw/validate - Shape validation ├── @tldraw/driver - Imperative API for programmatic control ├── @tldraw/sync - React bindings for multiplayer sync ├── @tldraw/sync-core - Core multiplayer sync engine ├── @tldraw/mermaid - Mermaid diagram integration ├── tldraw - Main package with full component └── create-tldraw - CLI for scaffolding projects Language Breakdown: • TypeScript: 87% • MDX: 7.7% • CSS: 2.3% • HTML: 1.3% • JavaScript: 1.3% • Shell: 0.3% • GLSL: 0.1% (for shaders) - Step 3
Prerequisites
Before setting up tldraw, ensure you have the required development environment and understand the key concepts.
# Node.js version requirement (^20.0.0) node --version npm --version # Enable Corepack for correct yarn version (tldraw uses yarn workspaces) npm i -g corepack corepack enable # Optional: For local development of the SDK yarn --version # Should be available after corepack enable # Optional: For multiplayer sync with Docker docker --version # Key concepts to understand: # • React components and hooks # • TypeScript (tldraw is fully typed) # • WebSocket fundamentals (for multiplayer) # • Canvas rendering basics - Step 4
Quick Start: Add tldraw to Your React App
The simplest way to add tldraw to a React application is to install the main
tldrawpackage and use the<Tldraw />component.# Install tldraw npm i tldraw # Or with yarn yarn add tldraw # Or with pnpm pnpm add tldraw # In your React component (App.tsx or App.js): import { Tldraw } from 'tldraw' import 'tldraw/tldraw.css' export default function App() { return ( <div style={{ position: 'fixed', inset: 0 }}> <Tldraw /> </div> ) } # The <Tldraw /> component provides: # • Full whiteboarding experience out of the box # • Default toolbar with shape tools # • Pencil, selection, shape, and text tools # • Keyboard shortcuts and gestures # • Autosave to localStorage # • Zoom, pan, and infinite canvas - Step 5
Alternative: Use Starter Kits
tldraw provides starter kits with
create-tldrawthat include pre-built custom shapes, tools, and UI components for common application types. This is the fastest way to get production-ready scaffolding.# Create a new tldraw project with a starter kit npx create-tldraw@latest # Available starter kits: # 1. Multiplayer - Real-time collaboration # Powered by @tldraw/sync and Cloudflare Durable Objects # Use case: Collaborative whiteboarding, team canvases # 2. Agent - AI-powered canvas apps # Canvas primitives for building with LLMs # Use case: AI assistants that read/write canvas content # 3. Workflow - Node-based visual builder # Drag-and-drop automation pipelines # Use case: No-code platforms, workflow automation # 4. Chat - Canvas + AI chat # Sketch, annotate, and markup alongside conversations # Use case: Visual AI assistants, design chat # 5. Image pipeline - Node-based image generation # Visual builder for image generation pipelines # Use case: Image processing workflows # 6. Branching chat - Visual conversation branching # Explore and compare conversation paths # Use case: AI chat exploration, decision trees # 7. Shader - Interactive WebGL shaders # Canvas interactions respond with shaders # Use case: Visual effects, generative art - Step 6
Set Up Multiplayer Sync with @tldraw/sync
For real-time collaboration, use
@tldraw/syncwhich provides WebSocket-based state synchronization. The default setup uses Cloudflare Durable Objects for self-hosted multiplayer.# Install sync packages npm i @tldraw/sync @tldraw/sync-core # Or with yarn yarn add @tldraw/sync @tldraw/sync-core # @tldraw/sync provides: # • React bindings for multiplayer # • WebSocket integration via @tldraw/sync-core # • State synchronization across clients # • Conflict resolution # • Offline support with local-first sync # Dependencies in @tldraw/sync: # - @tldraw/state: workspace:* # - @tldraw/state-react: workspace:* # - @tldraw/sync-core: workspace:* # - @tldraw/utils: workspace:* # - nanoevents: ^7.0.1 # - ws: ^8.18.0 # - tldraw: workspace:* # Sync architecture: # Client → WebSocket → Durable Object → Client # • Bidirectional state updates # • CRDT-based conflict resolution # • Optimistic updates with reconciliation # • Minimal latency for collaborative editing - Step 7
Runtime API with @tldraw/editor
Use the Editor API to drive the canvas programmatically - create shapes, select tools, and manipulate canvas state directly.
// Import the Editor component import { Tldraw, useEditor } from 'tldraw' // Access the editor instance in your component function MyEditor() { const editor = useEditor() // Create a shape programmatically const createShape = () => { editor.createShapes([ editor.simpleShape({ type: 'rectangle', points: { x1: 100, y1: 100, x2: 200, y2: 200 } }) ]) } // Get current selection const getSelection = () => { return editor.getCurrentPageState().selectedIds } // Fit content to viewport const fitContent = () => { editor.zoomToFit() } // Delete selected shapes const deleteSelected = () => { const selectedIds = getSelection() ep.deleteShapes(selectedIds) } return ( <div style={{ position: 'fixed', inset: 0 }}> <Tldraw> {/* Custom UI slot */} <Tldraw.MyCustomUI> <button onClick={createShape}>Create Shape</button> <button onClick={deleteSelected}>Delete Selected</button> <button onClick={fitContent}>Fit to View</button> </Tldraw.MyCustomUI> </Tldraw> </div> ) } // The @tldraw/driver package provides imperative API: // npm i @tldraw/driver // Use for test automation, recording, or complex interactions - Step 8
Create Custom Shapes
Extend tldraw by creating custom shapes with custom rendering, interactions, and state. This enables domain-specific tools and features.
// Define a custom shape type import { shape } from '@tldraw/editor' // Custom shape registration const myCustomShape = { type: 'custom-shape', readonly: false, render: (props) => { const { props: shapeProps, bounds } = props return ( <div style={{ position: 'absolute', width: bounds.width, height: bounds.height, left: bounds.x, top: bounds.y, }} > {/* Custom rendering */} </div> ) }, } // Register the shape with the editor type CustomShapeType = ReturnType<typeof myCustomShape> // In your Tldraw component: <Tldraw shapes={[ ...defaultShapes, myCustomShape, ]} ></Tldraw> // Key shape features: // • Custom rendering via React components // • Interaction handlers (hit test, drag, etc.) // • Serializable state // • Style binding support // • Multi-selection behavior // • Keyboard shortcut customization - Step 9
Embed External Content
tldraw supports embedding external content through the Embed shape, which can render anything the browser supports. This is useful for webinars, tutorials, and media embedding.
// The <Embed /> shape supports: // • YouTube videos // • Figma files // • GitHub repositories // • Google Drive // • Loom // • Custom iframe embeds // Create an embed shape programmatically import { useEditor } from 'tldraw' function createYouTubeEmbed(editor, url: string) { const embed = editor.createEmbeddedMedia({ type: 'youtube', mediaId: extractYouTubeVideoId(url), origin: { point: { x: 0, y: 0 }, width: 800, height: 450, }, }) editor.createShapes([embed]) } function extractYouTubeVideoId(url: string): string { const match = url.match(/(?:v=|/)([a-zA-Z0-9_-]{11})/) return match ? match[1] : '' } // Custom embed handler registration: // 1. Create an Embed handler // 2. Implement the render method // 3. Register with Tldraw component <Tldraw embeds={[ // Custom embed handlers here { type: 'custom-embed', test: (url) => url.startsWith('https://yourdomain.com'), render: ({ url, dimensions }) => ( <iframe src={url} width={dimensions.width} height={dimensions.height} frameBorder="0" /> ), }, ]} ></Tldraw> // Embed shapes support: // • Auto-sizing // • Responsive embeds // • Previews and thumbnails // • Click-through interaction - Step 10
AI Integration with tldraw
tldraw provides primitives for building AI-powered canvas applications. Connect LLMs to read, interpret, and modify canvas content.
// AI integration patterns with tldraw: // 1. Canvas primitives for LLM context function getCanvasContextForAI(editor) { return { shapes: editor.getShapesInCurrentPage().map(shape => ({ type: shape.type, props: shape.props, point: shape.point, id: shape.id, })), selection: editor.getCurrentPageState().selectedIds, canvasBounds: editor.getCurrentPageState(), } } // 2. AI assistant that can modify canvas async function askAIAssistant(question: string, editor) { const canvasContext = getCanvasContextForAI(editor) const prompt = ` You have access to a whiteboard canvas. Current shapes: ${JSON.stringify(canvasContext.shapes)} How can you answer: "${question}"? Respond with canvas modifications if needed. ` const response = await fetch('/api/assistant', { method: 'POST', body: JSON.stringify({ prompt }), }) const modifications = await response.json() applyCanvasModifications(editor, modifications) } // 3. Image annotation with AI async function annotateImage(imageUrl: string, editor) { const analysis = await analyzeImageWithAI(imageUrl) // Create annotation shapes based on AI analysis for (const annotation of analysis.annotations) { editor.createShapes([ editor.simpleShape({ type: 'text', points: annotation.boundingBox, text: annotation.label, }), ]) } } // Use the Agent starter kit for: // • AI-powered canvas manipulation // • Natural language shape creation // • Smart suggestions based on context - Step 11
Production Configuration
For production deployments, configure licensing, environment variables, and optimization settings.
# Production setup checklist: # 1. License Key (Required for Production) # tldraw SDK is free for development # Production use requires a license key # Get your key at: https://tldraw.dev/pricing import { Tldraw } from 'tldraw' <Tldraw licenseKey="YOUR_LICENSE_KEY" /> # 2. Environment Variables # For multiplayer sync with Cloudflare Workers: export CLOUDFLARE_ACCOUNT_ID="your-account-id" export CLOUDFLARE_API_TOKEN="your-api-token" export WRANGLER_TOML_PATH="../wrangler.toml" # 3. Bundle Optimization (Tree Shaking) # tldraw supports tree-shaking for smaller bundles # Import only what you need: import { Tldraw, useEditor } from 'tldraw' // This excludes unused features automatically # 4. Performance Considerations # • Use React.memo for custom components # • Lazy load heavy embeds # • Implement virtualization for large canvases # • Use canvas pooling for dynamic shapes # 5. Security Best Practices # • Sanitize user-generated content # • Use proper CORS policies for embeds # • Implement rate limiting for sync endpoints # • Validate all shape data on server # 6. Analytics and Monitoring # Track usage with custom handlers: <Tldraw onChange={(e) => { // Track shape creation, deletion, etc. analytics.track('canvas_change', { eventType: e.type }) }} /> # 7. Testing # Use @tldraw/driver for automated testing import { Driver } from '@tldraw/driver' const driver = new Driver() await driver.paste( await driver.readShapeCode('rectangle') ) await driver.clickAt(100, 100) # 8. Bundle Size Optimization # Analyze bundle with Webpack Bundle Analyzer npm install --save-dev webpack-bundle-analyzer - Step 12
Deployment Options
tldraw supports various deployment scenarios, from client-side only to full multiplayer with Cloudflare.
# Option 1: Static Deployment (NPM Package) # Best for: Single-user apps, demos # Install and use directly in React app npm i tldraw # Deploy with: # • Vercel # • Netlify # • Cloudflare Pages # • AWS Amplify # Option 2: Multiplayer with Cloudflare Workers # Best for: Collaborative apps, production # Server-side setup: # 1. Set up Cloudflare Worker with Durable Objects # 2. Configure WebSocket routing # 3. Handle sync state management # wrangler.toml configuration: name = "tldraw-sync" main = "src/index.ts" compatibility_date = "2024-01-01" [[durable_objects.bindings]] name = "ROOM" class_name = "Room" # Deploy with: wrangler deploy # Option 3: Self-Hosted Backend # Best for: On-premise, data privacy # Use a WebSocket server: const WebSocket = require('ws') const server = new WebSocket.Server({ port: 8080 }) server.on('connection', (ws) => { ws.on('message', (message) => { // Broadcast to other connected clients broadcast(message) }) // Add to room, handle sync state room.addClient(ws) }) # Option 4: Dockerized Deployment # Best for: Container orchestration, scalable # Dockerfile for Multiplayer Server: FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . EXPOSE 8080 CMD ["node", "server.js"] # Or use the starter kit with Docker: docker build -t tldraw-app . docker run -p 80:80 tldraw-app # Option 5: Serverless with Vercel Functions # Best for: Small-scale multiplayer, easy scaling # pages/api/sync.ts import { TldrawSync } from '@tldraw/sync' export default async function handler(req, res) { const sync = new TldrawSync(req.body.documentId) return sync.handle(request, response) } # Deployment checklist: # ✓ Set up SSL (HTTPS required for production) # ✓ Configure CORS for WebSocket endpoints # ✓ Set up monitoring and logging # ✓ Implement backup strategy for saved canvases # ✓ Configure CDN for static assets # ✓ Set up rate limiting - Step 13
Key Dependencies and Tech Stack
Understanding the technology stack and dependencies helps with debugging and customization.
Main Package (tldraw) Dependencies: • @tiptap/core: Rich text editor foundation • @tiptap/react: React bindings for Tiptap • @tiptap/extensions: Code, highlight, lists • @tldraw/editor: Core editor engine • @tldraw/store: State management • classnames: Dynamic class name utilities • idb: IndexedDB wrapper for persistence • lz-string: String compression • radix-ui: UI component primitives @tldraw/editor Dependencies: • @tiptap/core, @tiptap/react: Rich text • @tldraw/state, @tldraw/state-react: State • @tldraw/store: Data layer • @tldraw/tlschema: Typed schema • @tldraw/utils: Utilities • @tldraw/validate: Validation • eventemitter3: Event system • idb: Local persistence • is-plain-object: Object utilities • rbush: Spatial indexing (for shape queries) @tldraw/sync Dependencies: • nanoevents: Minimal event emitter • ws: WebSocket client • @tldraw/sync-core: Sync engine Monorepo Dev Dependencies: • @swc/core: Fast TypeScript compilation • @vitest/ui: Testing UI • @microsoft/api-extractor: API documentation • docx: Word document export • adm-zip: Archive handling Tools and Frameworks: • TypeScript: Type-safe JavaScript • React 18: UI framework • Yarn (via corepack): Package manager • SWC: Rust-based compiler • Vite: Development server • Vitest: Testing framework Build the SDK: # Install dependencies yarn # Start dev server yarn dev # Build all packages yarn build # Run tests yarn test # Check types yarn typecheck - Step 14
Custom Tools and Interactions
Create custom tools that extend tldraw's interaction model - from annotation tools to domain-specific drawing aids.
// Define a custom tool import { tool } from '@tldraw/editor' const myCustomTool = tool({ id: 'custom-tool', initial: 'idle', states: [ { id: 'idle', onPointerDown: ({ editor, info }) => { // Start drawing when user clicks return 'drawing' }, }, { id: 'drawing', // Update on every pointer move onPointerMove: ({ editor, info }) => { const shape = getInfo().shape editor.updateShapes([ { id: shape.id, type: shape.type, props: { ... }, }, ]) return 'drawing' }, // Finalize on pointer up onPointerUp: ({ editor }) => { return 'idle' }, }, ], }) // Register custom tool <Tldraw tools={[ ...defaultTools, myCustomTool, ]} ></Tldraw> // Tool features: // • State machine for interactions // • Pointer event handling // • Keyboard interactions // • Undo/redo integration // • Multi-state tools // Example: Drawing tool const drawingTool = tool({ id: 'drawing', initial: 'drawing', states: [ { id: 'drawing', onPointerDown: ({ editor, info }) => { // Create new path const path = editor.createPathFromGeometry({ line: info.line, }).id return { state: 'drawing', path } }, onPointerMove: ({ editor, info, state }) => { // Extend the path const pathShape = editor.getShapesById([state.path])[0] editor.updateShapes([ { ...pathShape, props: { ...props, points: [...points, info.point] }, }, ]) return 'drawing' }, onPointerUp: () => 'idle', }, ], }) // Custom gesture handlers: <Tldraw onKey={(info) => { if (info.key === 's' && info.mods.cmd) { // Save the canvas saveCanvas() return 'handled' } }} /> - Step 15
Testing and Debugging
Test tldraw applications effectively using the Driver package and React Testing Library.
// Install testing dependencies npm i -D @testing-library/react @testing-library/jest-dom vitest // Using @tldraw/driver for integration tests import { describe, it, expect } from 'vitest' import { Driver } from '@tldraw/driver' describe('tldraw canvas tests', async () => { it('creates a rectangle', async () => { const driver = new Driver() // Enable rectangle tool await driver.rectangle({ points: { x1: 100, y1: 100, x2: 200, y2: 200 }, }) // Verify shape exists const shapes = await driver.getShapes() expect(shapes.length).toBe(1) expect(shapes[0].type).toBe('rectangle') }) it('handles multi-shape operations', async () => { const driver = new Driver() // Create multiple shapes await driver.rectangle({ points: { x1: 0, y1: 0, x2: 100, y2: 100 }, }) await driver.circle({ points: { x1: 50, y1: 50, r: 25 }, }) // Select and delete await driver.selectShapes([shape1, shape2]) await driver.press('Delete') expect(await driver.getShapes()).toHaveLength(0) }) }) // Component testing with React Testing Library import { render, screen, fireEvent } from '@testing-library/react' import { Tldraw } from 'tldraw' import { MemoryEditorProvider, createEditor } from 'tldraw' describe('Custom Tldraw component', () => { it('renders the canvas', () => { const editor = createEditor() render( <MemoryEditorProvider editor={editor}> <Tldraw /> </MemoryEditorProvider> ) expect(screen.getByTestId('tl-icon-container')).toBeInTheDocument() }) it('handles shape creation', () => { // Mock the editor API const mockCreateShapes = vi.fn() const editor = createEditor() editor.createShapes = mockCreateShapes // Test shape creation await userEvent.click(screen.getByText('Rectangle')) await userEvent.click(screen.getByTestId('canvas')) expect(mockCreateShapes).toHaveBeenCalled() }) }) // Debugging tips: // 1. Use React DevTools with tldraw plugins // 2. Enable tldraw console logging for sync events // 3. Use the built-in debug mode: <Tldraw> <DebugPanel /> </Tldraw> // 4. Use browser dev tools for rendering // 5. Check Console for sync errors // 6. Network tab for WebSocket connections - Step 16
Use Cases and Target Audiences
tldraw is well-suited for various application types across different domains.
Primary Use Cases: 1. Collaborative Whiteboarding - Remote team brainstorming - Design reviews - Architectural diagrams - Meeting notes and planning 2. Educational Tools - Online teaching platforms - Digital math workspaces - Interactive lessons - Exam and assessment tools 3. Design and Prototyping - UI/UX wireframing - Rapid prototyping - User journey mapping - Flowchart creation 4. Software Development - System architecture diagrams - API documentation - Database schema design - Deployment diagrams 5. Project Management - Visual Kanban boards - Gantt chart alternatives - Sprint planning - Retrospective canvases 6. AI-Powered Applications - Visual AI assistants - Natural language to diagram - Canvas-based data analysis - Smart suggestions Target Audiences: • Software developers building canvas apps • Design tools teams • Education technology companies • Collaboration platform builders • AI/ML application developers • Low-code/no-code platform creators • Enterprise software vendors Integration Patterns: • Whiteboard in meetings (Zoom, Slack) • Design system components • Documentation viewers • Data visualization tools • Workflow automation builders • Visual programming languages Industries: • Technology & SaaS • Education & EdTech • Healthcare (diagrams, planning) • Construction (floor plans) • Manufacturing (schematics) • Finance (process diagrams) - Step 17
Additional Resources
Explore these resources to deepen your understanding of tldraw and maximize its capabilities.
Official Resources: • Main Website: https://tldraw.dev • Documentation: https://tldraw.dev/docs • Examples: https://tldraw.dev/examples • Starter Kits: https://tldraw.dev/starter-kits/overview • API Reference: https://tldraw.dev/api GitHub: • Main Repository: https://github.com/tldraw/tldraw • Issues: https://github.com/tldraw/tldraw/issues • Discussions: https://github.com/tldraw/tldraw/discussions • Wiki: https://github.com/tldraw/tldraw/wiki Community: • Discord: https://discord.tldraw.com • Twitter/X: https://twitter.com/tldraw Packages on NPM: • tldraw: https://www.npmjs.com/package/tldraw • @tldraw/editor: https://www.npmjs.com/package/@tldraw/editor • @tldraw/sync: https://www.npmjs.com/package/@tldraw/sync • @tldraw/sync-core: https://www.npmjs.com/package/@tldraw/sync-core • @tldraw/driver: https://www.npmjs.com/package/@tldraw/driver • create-tldraw: https://www.npmjs.com/package/create-tldraw Learning Resources: • DeepWiki: https://deepwiki.com/tldraw/tldraw • Contributing Guide: https://github.com/tldraw/tldraw/blob/main/CONTRIBUTING.md • License Information: https://github.com/tldraw/tldraw/blob/main/LICENSE.md Video Tutorials: • YouTube channel: Search "tldraw tutorials" • Example apps walkthrough • Feature demonstrations Advanced Topics: • Custom shape development • Multiplayer architecture • WebGL shader integration • Performance optimization • Accessibility considerations Related Projects: • @tldraw/tlschema: Type-safe schema • @tldraw/store: State management • Mermaid diagrams integration • Custom UI components
Feature requests
Sign in to suggest features or vote on existing ones.
No feature requests yet.
Discussion
Sign in to join the discussion.
No comments yet.