tldraw — Infinite Canvas SDK

## Overview

25 stars

Best use case

tldraw — Infinite Canvas SDK is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

## Overview

Teams using tldraw — Infinite Canvas SDK should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/tldraw/SKILL.md --create-dirs "https://raw.githubusercontent.com/ComeOnOliver/skillshub/main/skills/TerminalSkills/skills/tldraw/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/tldraw/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How tldraw — Infinite Canvas SDK Compares

Feature / Agenttldraw — Infinite Canvas SDKStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

## Overview

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# tldraw — Infinite Canvas SDK


## Overview


Tldraw, the open-source library for creating infinite canvas experiences in React applications. Helps developers embed collaborative whiteboards, diagram editors, and visual tools with tldraw's shape system, camera controls, and multiplayer support.


## Instructions

### Basic Setup

Embed a full-featured whiteboard in a React app:

```tsx
// src/components/Whiteboard.tsx — Basic tldraw canvas
import { Tldraw } from "tldraw";
import "tldraw/tldraw.css";

export function Whiteboard() {
  return (
    <div style={{ position: "fixed", inset: 0 }}>
      <Tldraw
        // Optional: persist to a backend
        persistenceKey="my-whiteboard"
        // Optional: customize available tools
        // tools={[...defaultTools, MyCustomTool]}
      />
    </div>
  );
}
```

### Custom Shapes

Define domain-specific shapes for your application:

```tsx
// src/shapes/TaskCardShape.tsx — Custom shape for a project management board
import {
  BaseBoxShapeUtil,
  TLBaseShape,
  HTMLContainer,
  T,
  DefaultColorStyle,
  ShapeUtil,
} from "tldraw";

// Define the shape's data structure
type TaskCard = TLBaseShape<
  "task-card",
  {
    title: string;
    description: string;
    assignee: string;
    priority: "low" | "medium" | "high" | "critical";
    status: "todo" | "in-progress" | "review" | "done";
    w: number;
    h: number;
  }
>;

// Shape utility — tells tldraw how to render and interact with the shape
export class TaskCardShapeUtil extends BaseBoxShapeUtil<TaskCard> {
  static override type = "task-card" as const;

  // Default values when a new task card is created
  getDefaultProps(): TaskCard["props"] {
    return {
      title: "New Task",
      description: "",
      assignee: "Unassigned",
      priority: "medium",
      status: "todo",
      w: 280,          // Default width in pixels
      h: 160,          // Default height
    };
  }

  // Render the shape as HTML (supports full React components)
  component(shape: TaskCard) {
    const priorityColors = {
      low: "#4ade80",
      medium: "#facc15",
      high: "#fb923c",
      critical: "#ef4444",
    };

    return (
      <HTMLContainer
        id={shape.id}
        style={{
          padding: "12px",
          borderRadius: "8px",
          border: `2px solid ${priorityColors[shape.props.priority]}`,
          backgroundColor: "white",
          boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
          display: "flex",
          flexDirection: "column",
          gap: "8px",
          pointerEvents: "all",    // Enable click/drag on the HTML content
        }}
      >
        <div style={{ display: "flex", justifyContent: "space-between" }}>
          <strong style={{ fontSize: "14px" }}>{shape.props.title}</strong>
          <span style={{
            fontSize: "10px",
            padding: "2px 6px",
            borderRadius: "4px",
            backgroundColor: priorityColors[shape.props.priority],
            color: "white",
          }}>
            {shape.props.priority}
          </span>
        </div>
        <p style={{ fontSize: "12px", color: "#666", margin: 0 }}>
          {shape.props.description || "No description"}
        </p>
        <div style={{ fontSize: "11px", color: "#999", marginTop: "auto" }}>
          👤 {shape.props.assignee} • {shape.props.status}
        </div>
      </HTMLContainer>
    );
  }

  // Render a simplified version when zoomed out (performance optimization)
  indicator(shape: TaskCard) {
    return <rect width={shape.props.w} height={shape.props.h} rx={8} />;
  }
}
```

### Accessing the Editor API

Programmatically control the canvas:

```typescript
// src/hooks/useCanvasActions.ts — Programmatic canvas manipulation
import { useEditor } from "tldraw";

export function useCanvasActions() {
  const editor = useEditor();

  // Create shapes programmatically
  function addTaskCard(title: string, x: number, y: number) {
    editor.createShape({
      type: "task-card",
      x,
      y,
      props: {
        title,
        description: "",
        assignee: "Unassigned",
        priority: "medium",
        status: "todo",
        w: 280,
        h: 160,
      },
    });
  }

  // Export canvas as image
  async function exportAsImage() {
    const shapeIds = editor.getCurrentPageShapeIds();
    if (shapeIds.size === 0) return;

    const blob = await editor.getSvg([...shapeIds], {
      scale: 2,            // 2x resolution
      background: true,    // Include background color
    });
    return blob;
  }

  // Zoom to fit all content
  function zoomToFit() {
    editor.zoomToFit({ animation: { duration: 300 } });
  }

  // Get all shapes of a specific type
  function getTaskCards() {
    return editor
      .getCurrentPageShapes()
      .filter((s) => s.type === "task-card") as TaskCard[];
  }

  // Update a shape's properties
  function updateTaskStatus(shapeId: string, status: string) {
    editor.updateShape({
      id: shapeId,
      type: "task-card",
      props: { status },
    });
  }

  // Listen to selection changes
  function onSelectionChange(callback: (shapes: TLShape[]) => void) {
    return editor.store.listen(
      () => {
        const selectedIds = editor.getSelectedShapeIds();
        const shapes = selectedIds.map((id) => editor.getShape(id)!);
        callback(shapes);
      },
      { source: "user", scope: "session" }
    );
  }

  return {
    addTaskCard,
    exportAsImage,
    zoomToFit,
    getTaskCards,
    updateTaskStatus,
    onSelectionChange,
  };
}
```

### Multiplayer with Yjs

Add real-time collaboration using Yjs:

```tsx
// src/components/MultiplayerWhiteboard.tsx — Collaborative canvas with Yjs sync
import { Tldraw, useEditor } from "tldraw";
import { useYjsStore } from "@tldraw/yjs";
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";

export function MultiplayerWhiteboard({ roomId }: { roomId: string }) {
  const doc = useMemo(() => new Y.Doc(), []);
  const provider = useMemo(
    () => new WebsocketProvider("wss://yjs.example.com", roomId, doc),
    [doc, roomId]
  );

  // useYjsStore bridges tldraw's store with Yjs for real-time sync
  const store = useYjsStore({
    yDoc: doc,
    provider,
    roomId,
  });

  return (
    <div style={{ position: "fixed", inset: 0 }}>
      <Tldraw
        store={store}
        // Users see each other's cursors and selections automatically
      />
    </div>
  );
}
```

### Snapshot and Restore

Save and load canvas state:

```typescript
// src/persistence/snapshots.ts — Save/load canvas state
import { Editor, TLStoreSnapshot } from "tldraw";

// Save the current canvas state as a JSON snapshot
function saveSnapshot(editor: Editor): TLStoreSnapshot {
  return editor.store.getSnapshot();
}

// Restore canvas from a saved snapshot
function loadSnapshot(editor: Editor, snapshot: TLStoreSnapshot) {
  editor.store.loadSnapshot(snapshot);
}

// Save to your backend
async function persistToServer(editor: Editor, documentId: string) {
  const snapshot = saveSnapshot(editor);
  await fetch(`/api/documents/${documentId}`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(snapshot),
  });
}

// Auto-save on changes (debounced)
function setupAutoSave(editor: Editor, documentId: string) {
  let timeout: NodeJS.Timeout;
  editor.store.listen(() => {
    clearTimeout(timeout);
    timeout = setTimeout(() => persistToServer(editor, documentId), 2000);
  }, { source: "user", scope: "document" });
}
```

## Installation

```bash
npm install tldraw

# For multiplayer
npm install @tldraw/yjs yjs y-websocket
```


## Examples


### Example 1: Setting up Tldraw with a custom configuration

**User request:**

```
I just installed Tldraw. Help me configure it for my TypeScript + React workflow with my preferred keybindings.
```

The agent creates the configuration file with TypeScript-aware settings, configures relevant plugins/extensions for React development, sets up keyboard shortcuts matching the user's preferences, and verifies the setup works correctly.

### Example 2: Extending Tldraw with custom functionality

**User request:**

```
I want to add a custom custom shapes to Tldraw. How do I build one?
```

The agent scaffolds the extension/plugin project, implements the core functionality following Tldraw's API patterns, adds configuration options, and provides testing instructions to verify it works end-to-end.


## Guidelines

1. **Full viewport container** — tldraw needs a container with explicit dimensions; `position: fixed; inset: 0` is the simplest approach
2. **Custom shapes for domain logic** — Don't force everything into draw/text; create shapes that represent your domain (tasks, nodes, cards)
3. **Use HTMLContainer for complex UI** — Custom shapes can render full React components, not just SVG
4. **Indicator for zoom performance** — Always implement `indicator()` — it renders when zoomed out instead of the full component
5. **Persist with snapshots** — Use `store.getSnapshot()` for server persistence; use Yjs for real-time sync
6. **Debounce auto-save** — Canvas changes fire rapidly during drawing; save every 2-3 seconds, not on every change
7. **Export as SVG, not PNG** — SVG exports are resolution-independent and much smaller in file size
8. **Test on touch devices** — tldraw supports touch natively, but custom shapes may need pointer event adjustments

Related Skills

Canvas Skill

25
from ComeOnOliver/skillshub

Display HTML content on connected Otto nodes (Mac app, iOS, Android).

infinite-gratitude

25
from ComeOnOliver/skillshub

Multi-agent research skill for parallel research execution (10 agents, battle-tested with real case studies).

json-canvas

25
from ComeOnOliver/skillshub

Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections. Use when working with .canvas files, creating visual canvases, mind maps, flowcharts, or when the user mentions Canvas files in Obsidian.

Lean Canvas — One-Page Business Model

25
from ComeOnOliver/skillshub

## Overview

Konva — 2D Canvas Graphics Framework

25
from ComeOnOliver/skillshub

## Overview

Canvas API

25
from ComeOnOliver/skillshub

The Canvas 2D API draws shapes, text, and images to a bitmap. In Node.js, the `canvas` package provides the same API for server-side image generation.

canvas-design

25
from ComeOnOliver/skillshub

Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.

canvas-automation

25
from ComeOnOliver/skillshub

Automate Canvas tasks via Rube MCP (Composio). Always search tools first for current schemas.

Daily Logs

25
from ComeOnOliver/skillshub

Record the user's daily activities, progress, decisions, and learnings in a structured, chronological format.

Socratic Method: The Dialectic Engine

25
from ComeOnOliver/skillshub

This skill transforms Claude into a Socratic agent — a cognitive partner who guides

Sokratische Methode: Die Dialektik-Maschine

25
from ComeOnOliver/skillshub

Dieser Skill verwandelt Claude in einen sokratischen Agenten — einen kognitiven Partner, der Nutzende durch systematisches Fragen zur Wissensentdeckung führt, anstatt direkt zu instruieren.

College Football Data (CFB)

25
from ComeOnOliver/skillshub

Before writing queries, consult `references/api-reference.md` for endpoints, conference IDs, team IDs, and data shapes.