Skip to main content

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.

Simple custom tool

CustomToolExample.tsx
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>
  )
}

Tool lifecycle methods

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
  }
}

Tools with child states

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')
  }
}

Adding tool to toolbar

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.