--- title: Tools created_at: 12/20/2025 updated_at: 12/29/2025 keywords: - tools - StateNode - state machine - events - interaction - overrides - custom tools - toolbar - tool lock status: published date: 12/20/2025 readability: 8 voice: 9 completeness: 9 accuracy: 10 notes: '' --- Tools in tldraw define how the editor responds to user input. Each tool handles one interaction mode: selecting shapes, drawing, panning the canvas. The editor has a single active tool at any time and routes all input events through it. When you click the hand icon in the toolbar, the editor transitions from the select tool to the hand tool, and the canvas starts responding to drags by panning. You implement tools as state machines using the [StateNode](?) class. Multi-step interactions map onto child states: when you resize a shape with the select tool, the tool moves through `idle`, `pointing_resize_handle`, and `resizing`. Each state handles different events and can transition to other states. ## How it works Tools are organized in a hierarchical state machine where each node can handle events and contain child states. The editor creates a root state that contains all tools as children. When an input event occurs, it flows down from the root through the currently active tool and its active child state. Each state node has an id, optional children, and methods for handling events. State nodes come in three types: root nodes that contain tools, branch nodes that have child states, and leaf nodes that perform actual work. Tools themselves are typically branch nodes with child states representing different phases of an interaction. When a state becomes active, its `onEnter` method runs. When it becomes inactive, its `onExit` method runs. Between these lifecycle events, the state handles input through event methods like `onPointerDown`, `onPointerMove`, and `onKeyDown`. A state that doesn't implement a handler skips the event, and the event still continues down to the active child state, so a parent and its child can both respond. You trigger transitions between states explicitly through [StateNode#transition](?). When the select tool's idle state detects a pointer down on a shape, it calls `this.parent.transition('pointing_shape', info)` to move to the pointing state. The transition runs the old state's `onExit` and the new state's `onEnter`. ## Key concepts ### State hierarchy Tools exist in a tree structure starting from a root node. The root contains all available tools like select, hand, eraser, and draw. Each tool can contain child states for different phases of its interaction. For example, the select tool has children including `idle`, `pointing_shape`, `translating`, `resizing`, and `rotating`. When the select tool is active and the user starts dragging a shape, the active path becomes `select.translating`. The hierarchy lets a tool share behavior across its child states. The select tool's `onEnter` and `onExit` set up and tear down state that every child uses, while the child states handle pointer and keyboard interactions. ### Event handling State nodes implement event handler methods that match input event types. The handlers receive an info object containing event details like pointer position, keyboard modifiers, and the event target. The full set is `onPointerDown`, `onPointerMove`, `onPointerUp`, `onLongPress`, `onDoubleClick`, `onRightClick`, `onMiddleClick`, `onKeyDown`, `onKeyUp`, `onKeyRepeat`, `onWheel`, `onCancel`, `onComplete`, `onInterrupt`, and `onTick` for animation frame updates. The hand tool's dragging state implements `onPointerMove` to update the camera position as the user drags. ### State transitions You can transition to a direct child using just its id, or to deeper descendants using dot notation like `'crop.pointing_crop_handle'`. Transitions carry information through their second parameter. When transitioning from idle to pointing, the pointer event info passes along so the pointing state knows where the interaction started. This data is available in both the exit handler of the old state and the enter handler of the new state. ### Tool registration Tools are registered with the editor through the root state. The `@tldraw/editor` package provides only the root state with no tools. The `tldraw` package adds its full suite of tools. Custom tools are added through the `tools` prop, described below. [Editor#setCurrentTool](?) transitions the root state to a different tool by id. [Editor#getCurrentTool](?) returns the active tool state node, and [Editor#getCurrentToolId](?) returns its id. ### Event targets Event info objects include a `target` property indicating what the user interacted with: `canvas`, `shape`, `handle`, `selection`, or `overlay`. The canvas dispatches every pointer event with `target: 'canvas'`. The select tool's idle state hit-tests the pointer position and re-dispatches the event to itself with a more specific target, then transitions to the matching child state: `pointing_shape` for a shape, `pointing_canvas` for empty canvas. Custom tools that need to know what's under the pointer do their own hit testing with methods like [Editor#getShapeAtPoint](?). ### Tool lock Tool lock keeps the current tool active after completing an action. Normally, tools like geo, arrow, or note return to the select tool after creating a shape. With tool lock enabled, the tool stays active so you can create multiple shapes without reselecting the tool each time. Tool lock is stored in instance state: ```ts // Check if tool lock is enabled editor.getInstanceState().isToolLocked // Enable tool lock editor.updateInstanceState({ isToolLocked: true }) // Toggle tool lock const current = editor.getInstanceState().isToolLocked editor.updateInstanceState({ isToolLocked: !current }) ``` Tool lock is not enforced by the state machine. Custom tools check `isToolLocked` themselves when deciding where to go after completing their action: ```ts if (this.editor.getInstanceState().isToolLocked) { this.parent.transition('idle') } else { this.editor.setCurrentTool('select') } ``` ## Creating custom tools To create a custom tool, extend the [StateNode](?) class and implement the static properties and event handlers you need. The simplest tool has no child states and handles events directly. Register it with the `tools` prop: ```tsx import { StateNode, TLPointerEventInfo, Tldraw } from 'tldraw' import 'tldraw/tldraw.css' class MeasureTool extends StateNode { static override id = 'measure' override onEnter() { this.editor.setCursor({ type: 'cross', rotation: 0 }) } override onPointerDown(info: TLPointerEventInfo) { const start = this.editor.inputs.getCurrentPagePoint() // Start measuring from this point } override onPointerUp(info: TLPointerEventInfo) { // Finalize measurement and return to select tool this.editor.setCurrentTool('select') } } const customTools = [MeasureTool] export default function App() { return (