Click detection

In tldraw, the click detection system lets you respond to double, triple, and quadruple clicks. The ClickManager tracks consecutive pointer down events using a state machine, dispatching click events when timing and distance thresholds are met.

This powers text editing features like word selection on double-click and paragraph selection on triple-click. You can use these events to add custom multi-click behaviors in your own shapes and tools.

How it works

When a pointer down event occurs, the manager either starts a new sequence or advances to the next click level. Each state has a timeout that determines how long to wait for the next click before settling on the current level.

Two timeout durations control the detection speed. The first click uses doubleClickDurationMs (450ms by default), giving users time to initiate a double-click sequence. Subsequent clicks use the shorter multiClickDurationMs (200ms by default), requiring faster input for triple and quadruple clicks. This pattern matches common operating system behavior.

State transitions

The click state machine progresses through these states:

StateDescription
idleNo active click sequence
pendingDoubleFirst click registered, waiting for second
pendingTripleSecond click registered, waiting for third
pendingQuadrupleThird click registered, waiting for fourth
pendingOverflowFourth click registered, waiting for fifth
overflowMore than four clicks detected

When a pointer down event arrives, the state advances to the next pending state and sets a timeout. If the timeout expires before the next click, the manager dispatches a settle event for the current click level and returns to idle. If another pointer down arrives before the timeout, the state advances and a click event is dispatched immediately.

Distance validation

Consecutive clicks must occur within a maximum distance of 40 pixels (screen space). If pointer down events are farther apart, the click sequence resets to idle.

Click event phases

Click events are dispatched with a phase property indicating when in the sequence the event fired:

PhaseWhen it fires
downImmediately when a multi-click is detected during pointer down
upDuring pointer up for multi-clicks that are still pending
settleWhen the timeout expires without further clicks

The phase system lets tools and shapes respond at different points in the click sequence. For example, the hand tool waits for the settle phase before zooming in—this avoids triggering a zoom if the user is about to triple-click.

Movement cancellation

If the pointer moves too far during a pending click sequence, the system cancels the sequence and returns to idle. This prevents multi-click detection during click-drag operations. The movement threshold differs for coarse pointers (touchscreens) and fine pointers (mouse, stylus).

Handling click events

Tools receive click events through handler methods defined in the TLEventHandlers interface. Here's a complete example of a custom tool that zooms in on double-click:

import { StateNode, TLClickEventInfo, Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'

class ZoomTool extends StateNode {
	static override id = 'zoom'

	override onDoubleClick(info: TLClickEventInfo) {
		if (info.phase !== 'settle') return
		this.editor.zoomIn(info.point, { animation: { duration: 200 } })
	}

	override onTripleClick(info: TLClickEventInfo) {
		if (info.phase !== 'settle') return
		this.editor.zoomOut(info.point, { animation: { duration: 200 } })
	}
}

const customTools = [ZoomTool]

export default function App() {
	return (
		<div style={{ position: 'fixed', inset: 0 }}>
			<Tldraw
				tools={customTools}
				onMount={(editor) => {
					editor.setCurrentTool('zoom')
				}}
			/>
		</div>
	)
}

Tools can handle StateNode.onDoubleClick, StateNode.onTripleClick, and StateNode.onQuadrupleClick events. Each receives a TLClickEventInfo object with details about the click.

Shape utilities handle double-clicks through the ShapeUtil.onDoubleClick method. Return a partial shape object to apply changes:

override onDoubleClick(shape: MyShape) {
	return {
		id: shape.id,
		type: shape.type,
		props: { expanded: !shape.props.expanded },
	}
}

The TLClickEventInfo type includes these properties:

PropertyTypeDescription
type'click'Event type identifier
name'double_click' | 'triple_click' | 'quadruple_click'Which multi-click event this is
pointVecLikePointer position in client space
pointerIdnumberUnique identifier for the pointer
buttonnumberMouse button (0 = left, 1 = middle, 2 = right)
phase'down' | 'up' | 'settle'When in the click sequence this fired
target'canvas' | 'selection' | 'shape' | 'handle'What was clicked
shapeTLShape | undefinedThe shape, when target is 'shape' or 'handle'
handleTLHandle | undefinedThe handle, when target is 'handle'
shiftKeybooleanWhether Shift was held
altKeybooleanWhether Alt/Option was held
ctrlKeybooleanWhether Control was held
metaKeybooleanWhether Meta/Command was held
accelKeybooleanPlatform accelerator key (Cmd on Mac, Ctrl on Windows)

Timing configuration

Click timing is configured through the editor's options:

OptionDefaultDescription
doubleClickDurationMs450msTime window for the first click to become a double-click
multiClickDurationMs200msTime window for subsequent clicks in the sequence
  • Canvas events — logs pointer events including click sequences to understand the event flow
  • Custom double-click behavior — overrides the default double-click handler in the SelectTool
  • Custom shape — implements onDoubleClick and other click handlers in custom shapes
Prev
Camera system
Next
Clipboard