Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tldraw/tldraw/llms.txt
Use this file to discover all available pages before exploring further.
Tools in tldraw are nodes in the editor’s state machine. You can create custom tools by extending the StateNode class and overriding its methods.
import { StateNode, Tldraw, toRichText } from 'tldraw'
import 'tldraw/tldraw.css'
const OFFSET = 12
// [1] Create the tool class
class StickerTool extends StateNode {
static override id = 'sticker'
// Called when tool is activated
override onEnter() {
this.editor.setCursor({ type: 'cross', rotation: 0 })
}
// Called when user clicks
override onPointerDown() {
const currentPagePoint = this.editor.inputs.getCurrentPagePoint()
this.editor.createShape({
type: 'text',
x: currentPagePoint.x - OFFSET,
y: currentPagePoint.y - OFFSET,
props: { richText: toRichText('❤️') },
})
}
}
// [2] Pass to Tldraw component
const customTools = [StickerTool]
export default function CustomToolExample() {
return (
<div className="tldraw__editor">
<Tldraw
tools={customTools}
initialState="sticker"
hideUi
onMount={(editor) => {
editor.createShape({
type: 'text',
x: 100,
y: 100,
props: { richText: toRichText('Click anywhere to add a sticker') },
})
}}
/>
</div>
)
}
onEnter / onExit
Called when the tool becomes active or inactive:
class MyTool extends StateNode {
static override id = 'my-tool'
override onEnter() {
// Set cursor, prepare state, etc.
this.editor.setCursor({ type: 'cross', rotation: 0 })
}
override onExit() {
// Clean up
this.editor.setCursor({ type: 'default', rotation: 0 })
}
}
Pointer events
class DrawingTool extends StateNode {
static override id = 'drawing-tool'
override onPointerDown() {
const point = this.editor.inputs.getCurrentPagePoint()
// Start drawing
}
override onPointerMove() {
const point = this.editor.inputs.getCurrentPagePoint()
// Update drawing
}
override onPointerUp() {
// Finish drawing
}
}
Keyboard events
class MyTool extends StateNode {
static override id = 'my-tool'
override onKeyDown(info) {
if (info.key === 'Escape') {
this.editor.setCurrentTool('select')
}
}
override onKeyUp(info) {
// Handle key release
}
}
Complex tools can have child states for different interaction phases:
import { StateNode, TLStateNodeConstructor } from 'tldraw'
class IdleState extends StateNode {
static override id = 'idle'
override onPointerDown() {
this.parent.transition('drawing')
}
}
class DrawingState extends StateNode {
static override id = 'drawing'
override onPointerMove() {
// Update shape while drawing
}
override onPointerUp() {
this.parent.transition('idle')
}
}
class ComplexTool extends StateNode {
static override id = 'complex-tool'
static override initial = 'idle'
static override children = (): TLStateNodeConstructor[] => [
IdleState,
DrawingState,
]
}
Accessing editor
Inside your tool, access the editor instance with this.editor:
class MyTool extends StateNode {
static override id = 'my-tool'
override onPointerDown() {
// Create shapes
this.editor.createShape({ type: 'geo', x: 0, y: 0 })
// Get input position
const point = this.editor.inputs.getCurrentPagePoint()
// Set cursor
this.editor.setCursor({ type: 'cross', rotation: 0 })
// Switch tools
this.editor.setCurrentTool('select')
}
}
To add your custom tool to the toolbar, you’ll need to customize the UI:
import { Tldraw, useEditor, TldrawUiMenuItem, useTools } from 'tldraw'
function CustomToolbar() {
const editor = useEditor()
const tools = useTools()
return (
<div className="custom-toolbar">
<button onClick={() => editor.setCurrentTool('sticker')}>
Add Sticker
</button>
</div>
)
}
export default function Example() {
return (
<div className="tldraw__editor">
<Tldraw tools={[StickerTool]}>
<CustomToolbar />
</Tldraw>
</div>
)
}
Available event handlers
onPointerDown(info: TLPointerEventInfo)
onPointerMove(info: TLPointerEventInfo)
onPointerUp(info: TLPointerEventInfo)
onPointerEnter(info: TLPointerEventInfo)
onPointerLeave(info: TLPointerEventInfo)
Transitions
Switch between tools or states using transitions:
// Switch to a different tool
this.editor.setCurrentTool('select')
// Transition to child state (if tool has children)
this.parent.transition('drawing')
Tools are implemented as state machines using the StateNode class. This provides a robust framework for handling complex interactions and tool states.