tldraw
Reactアプリに無限キャンバス体験を組み込むためのオープンソースライブラリtldrawについて、開発者が共同ホワイトボードや図形エディタなどのビジュアルツールを、その形状システムやマルチプレイヤーサポートを活用して実装できるよう専門的なガイダンスを提供するSkill。
📜 元の英語説明(参考)
Expert guidance for 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.
🇯🇵 日本人クリエイター向け解説
Reactアプリに無限キャンバス体験を組み込むためのオープンソースライブラリtldrawについて、開発者が共同ホワイトボードや図形エディタなどのビジュアルツールを、その形状システムやマルチプレイヤーサポートを活用して実装できるよう専門的なガイダンスを提供するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o tldraw.zip https://jpskill.com/download/15485.zip && unzip -o tldraw.zip && rm tldraw.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/15485.zip -OutFile "$d\tldraw.zip"; Expand-Archive "$d\tldraw.zip" -DestinationPath $d -Force; ri "$d\tldraw.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
tldraw.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
tldrawフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
tldraw — 無限キャンバス SDK
概要
Tldraw は、React アプリケーションで無限キャンバス体験を作成するためのオープンソースライブラリです。Tldraw のシェイプシステム、カメラコントロール、およびマルチプレイヤーサポートにより、開発者は共同ホワイトボード、ダイアグラムエディタ、およびビジュアルツールを埋め込むことができます。
手順
基本的な設定
React アプリにフル機能のホワイトボードを埋め込みます。
// src/components/Whiteboard.tsx — 基本的な tldraw キャンバス
import { Tldraw } from "tldraw";
import "tldraw/tldraw.css";
export function Whiteboard() {
return (
<div style={{ position: "fixed", inset: 0 }}>
<Tldraw
// オプション: バックエンドに永続化
persistenceKey="my-whiteboard"
// オプション: 利用可能なツールをカスタマイズ
// tools={[...defaultTools, MyCustomTool]}
/>
</div>
);
}
カスタムシェイプ
アプリケーション固有のシェイプを定義します。
// src/shapes/TaskCardShape.tsx — プロジェクト管理ボード用のカスタムシェイプ
import {
BaseBoxShapeUtil,
TLBaseShape,
HTMLContainer,
T,
DefaultColorStyle,
ShapeUtil,
} from "tldraw";
// シェイプのデータ構造を定義
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;
}
>;
// シェイプユーティリティ — tldraw にシェイプのレンダリング方法とインタラクション方法を指示
export class TaskCardShapeUtil extends BaseBoxShapeUtil<TaskCard> {
static override type = "task-card" as const;
// 新しいタスクカードが作成されたときのデフォルト値
getDefaultProps(): TaskCard["props"] {
return {
title: "New Task",
description: "",
assignee: "Unassigned",
priority: "medium",
status: "todo",
w: 280, // ピクセル単位のデフォルト幅
h: 160, // デフォルトの高さ
};
}
// シェイプを HTML としてレンダリング (完全な React コンポーネントをサポート)
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", // HTML コンテンツのクリック/ドラッグを有効にする
}}
>
<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>
);
}
// ズームアウト時に簡略化されたバージョンをレンダリング (パフォーマンスの最適化)
indicator(shape: TaskCard) {
return <rect width={shape.props.w} height={shape.props.h} rx={8} />;
}
}
Editor API へのアクセス
プログラムでキャンバスを制御します。
// src/hooks/useCanvasActions.ts — プログラムによるキャンバス操作
import { useEditor } from "tldraw";
export function useCanvasActions() {
const editor = useEditor();
// プログラムでシェイプを作成
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,
},
});
}
// キャンバスを画像としてエクスポート
async function exportAsImage() {
const shapeIds = editor.getCurrentPageShapeIds();
if (shapeIds.size === 0) return;
const blob = await editor.getSvg([...shapeIds], {
scale: 2, // 2 倍の解像度
background: true, // 背景色を含める
});
return blob;
}
// すべてのコンテンツに合わせてズーム
function zoomToFit() {
editor.zoomToFit({ animation: { duration: 300 } });
}
// 特定のタイプのすべてのシェイプを取得
function getTaskCards() {
return editor
.getCurrentPageShapes()
.filter((s) => s.type === "task-card") as TaskCard[];
}
// シェイプのプロパティを更新
function updateTaskStatus(shapeId: string, status: string) {
editor.updateShape({
id: shapeId,
type: "task-card",
props: { status },
});
}
// 選択の変更をリッスン
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,
};
}
Yjs を使用したマルチプレイヤー
Yjs を使用してリアルタイムコラボレーションを追加します。
// src/components/MultiplayerWhiteboard.tsx — Yjs 同期による共同キャンバス
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(
(
(原文がここで切り詰められています) 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
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:
// 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:
// 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:
// 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:
// 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:
// 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
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
- Full viewport container — tldraw needs a container with explicit dimensions;
position: fixed; inset: 0is the simplest approach - Custom shapes for domain logic — Don't force everything into draw/text; create shapes that represent your domain (tasks, nodes, cards)
- Use HTMLContainer for complex UI — Custom shapes can render full React components, not just SVG
- Indicator for zoom performance — Always implement
indicator()— it renders when zoomed out instead of the full component - Persist with snapshots — Use
store.getSnapshot()for server persistence; use Yjs for real-time sync - Debounce auto-save — Canvas changes fire rapidly during drawing; save every 2-3 seconds, not on every change
- Export as SVG, not PNG — SVG exports are resolution-independent and much smaller in file size
- Test on touch devices — tldraw supports touch natively, but custom shapes may need pointer event adjustments