Next release

This release adds a consolidated options prop, quick zoom navigation, a fill styles dropdown, a new TldrawUiSelect component, shape-aware binding checks, and an experimental canvas drop handler. It also includes 2D canvas rendering for shape indicators, R-tree spatial indexing, telestrator-style laser behavior, significant performance improvements for large canvases, and various bug fixes.

What's new

💥 Consolidated options prop (#7888)

The cameraOptions, textOptions, and deepLinks props on Tldraw, TldrawEditor, and TldrawImage are now consolidated into the options prop. The standalone props are deprecated but still work for backward compatibility.

// Before
<Tldraw cameraOptions={{ isLocked: true }} deepLinks textOptions={{ ... }} />

// After
<Tldraw options={{ camera: { isLocked: true }, deepLinks: true, text: { ... } }} />
Migration guide

Replace standalone props with equivalent options fields:

  • cameraOptions={...}options={{ camera: { ... } }}
  • textOptions={...}options={{ text: { ... } }}
  • deepLinks or deepLinks={...}options={{ deepLinks: true }} or options={{ deepLinks: { ... } }}

The deprecated props still work. When both the deprecated prop and the options field are provided, the options value takes precedence.

Quick zoom navigation (#7801, #7836)

Press z then hold Shift to zoom out and see the whole canvas ("eagle eye" view). A viewport brush appears showing where you'll zoom to. Move the cursor to pick a location and release Shift to zoom there. Press Escape to cancel and return to the original view.

Fill styles dropdown (#7885)

The style panel now exposes additional fill styles (pattern, fill, lined-fill) through a dropdown picker after the solid button. The first three fill options (none, semi, solid) remain as inline buttons.

TldrawUiSelect component (#7566)

New select dropdown primitive wrapping Radix UI's Select, following existing tldraw UI patterns.

<TldrawUiSelect value={value} onValueChange={setValue}>
	<TldrawUiSelectTrigger>
		<TldrawUiSelectValue placeholder="Select...">{value}</TldrawUiSelectValue>
	</TldrawUiSelectTrigger>
	<TldrawUiSelectContent>
		<TldrawUiSelectItem value="small" label="Small" icon="size-small" />
		<TldrawUiSelectItem value="medium" label="Medium" icon="size-medium" />
		<TldrawUiSelectItem value="large" label="Large" icon="size-large" />
	</TldrawUiSelectContent>
</TldrawUiSelect>

💥 ShapeUtil.canBind() now receives shapes (#7821)

ShapeUtil.canBind() now receives fromShape/toShape as TLShape | { type } unions instead of type strings via fromShapeType/toShapeType. This enables binding decisions based on shape props, not just types. Use 'id' in shape to narrow to TLShape when you need prop access.

Migration guide

Before:

canBind({ fromShapeType, toShapeType }) {
  return fromShapeType !== 'group'
}

After:

canBind({ fromShape, toShape }) {
  return fromShape.type !== 'group'
}

Experimental canvas drop handler (#7911)

A new experimental__onDropOnCanvas callback in TldrawOptions lets you intercept and customize drag-and-drop behavior on the canvas. The callback receives the page-space drop coordinates and the original drag event. Return true to prevent the default file/URL drop handling.

<Tldraw
	options={{
		experimental__onDropOnCanvas: (point, event) => {
			// Custom drop handling
			return true // prevents default behavior
		},
	}}
/>

2D canvas rendering for shape indicators (#7708)

Shape indicators (selection outlines, hover states) now render using a 2D canvas instead of SVG elements. This significantly improves performance when selecting or hovering over many shapes, with up to 25x faster rendering in some scenarios.

Custom shapes can opt into canvas indicators by implementing the new getIndicatorPath() method on their ShapeUtil:

class MyShapeUtil extends ShapeUtil<MyShape> {
	getIndicatorPath(shape: MyShape): TLIndicatorPath | null {
		return {
			path: new Path2D(),
			// optional clip path for complex shapes like arrows with labels
		}
	}

	// Return false to use the new canvas indicators (default is true for backwards compatibility)
	useLegacyIndicator(): boolean {
		return false
	}
}

Invert mouse wheel zoom direction (#7732)

Added a new user preference to invert mouse wheel zoom direction. Some users prefer "natural" scrolling behavior where scrolling down zooms out, which this option now enables.

Access it via Menu → Preferences → Input device → Invert mouse zoom.

R-tree spatial indexing (#7676)

Shape queries now use an R-tree (RBush) for O(log n) lookups instead of O(n) iteration. This significantly improves performance of brushing, scribble selection, and erasing operations, especially with many shapes on the canvas.

The spatial index is maintained internally and accessed through existing public methods like editor.getShapesAtPoint() and editor.getShapeAtPoint().

Telestrator-style laser pointer (#7681)

The laser pointer now behaves like a telestrator: all strokes remain visible while you're drawing and fade together when you stop. Previously, each stroke segment would fade independently, creating a trailing effect.

New API additions for controlling laser sessions:

// End the active laser session manually
editor.scribbles.endLaserSession()

// Check if a scribble belongs to the active session
editor.scribbles.isScribbleInLaserSession(scribbleId)

New options in TldrawOptions:

  • laserSessionTimeoutMs (default: 2000ms) - Inactivity duration before the laser session ends
  • laserMaxSessionDurationMs (default: 60000ms) - Maximum duration for a laser session

API changes

  • 💥 TldrawEditorBaseProps.cameraOptions, TldrawEditorBaseProps.textOptions, TldrawEditorBaseProps.deepLinks deprecated in favor of options.camera, options.text, options.deepLinks. (#7888)
  • 💥 TLShapeUtilCanBindOpts.fromShapeType and toShapeType replaced with fromShape and toShape accepting TLShape | { type }. (#7821)
  • Remove editor.spatialIndex from public API. The spatial index is now internal; use editor.getShapesAtPoint() and editor.getShapeAtPoint() for shape queries. (#7699)
  • Add TldrawOptions.camera, TldrawOptions.text, and TldrawOptions.deepLinks fields to the options prop. (#7888)
  • Add TldrawUiSelect, TldrawUiSelectTrigger, TldrawUiSelectValue, TldrawUiSelectContent, TldrawUiSelectItem components and associated prop types. Add iconTypes export for enumerating available icons. (#7566)
  • Add useCanApplySelectionAction() hook for checking if selection actions should be enabled. (#7811)
  • Add TLInstance.cameraState: 'idle' | 'moving' for tracking camera movement state. (#7826)
  • Add quickZoomPreservesScreenBounds to TldrawOptions. (#7836)
  • Add fillExtra to STYLES object for additional fill style options. (#7885)
  • Make Editor.getShapeIdsInsideBounds() public. (#7863)
  • Add ShapeUtil.getIndicatorPath() method and TLIndicatorPath type for canvas-based indicator rendering. Add ShapeUtil.useLegacyIndicator() to control whether shapes use SVG or canvas indicators. (#7708)
  • Add isZoomDirectionInverted to TLUserPreferences interface and UserPreferencesManager.getIsZoomDirectionInverted() method. Add ToggleInvertZoomItem component export and toggle-invert-zoom action. (#7732)
  • Add complete to TL_SCRIBBLE_STATES enum and ScribbleManager.complete(id) method for marking scribbles as complete before fading. (#7760)
  • Add ScribbleManager.endLaserSession() and ScribbleManager.isScribbleInLaserSession() methods for controlling laser sessions. Add laserSessionTimeoutMs and laserMaxSessionDurationMs to TldrawOptions. (#7681)
  • Add FpsScheduler class to create FPS-throttled function queues with configurable target rates. (#7418)
  • Add TldrawOptions.experimental__onDropOnCanvas callback for intercepting canvas drop events. (#7911)

Improvements

  • Improve performance in large canvases and multiplayer rooms by optimizing Set comparisons, reducing memory allocations, and caching string hashes. (#7840)
  • Improve panning performance in large documents by skipping hover hit-testing during camera movement. (#7826)
  • Improve performance when translating arrows together with bound shapes by skipping unnecessary reparenting. (#7733)
  • Add R-tree spatial indexing for O(log n) shape queries, improving performance of brushing, selection, and erasing with many shapes. (#7676)
  • Improve laser pointer to keep all strokes visible during a session (telestrator pattern), with strokes fading together when drawing stops. (#7681)
  • Improve laser pointer strokes with proper taper when lifting the pointer by adding a 'complete' state to the scribble lifecycle. (#7760)
  • Improve quick zoom to use zoom-to-fit instead of fixed 5%, add "quick peek" behavior, and add edge scrolling. (#7836)
  • Remove core-js polyfill dependency from @tldraw/editor. (#7769)
  • Reduce network traffic by squashing pending push requests before sending, so rapid edits result in fewer network calls. (#7724)
  • Reduce solo-mode network traffic by using a dedicated FPS-based scheduler that throttles to 1 FPS when no collaborators are present. (#7657)
  • Improve performance by separating UI and network scheduling into independent queues with configurable target FPS. (#7418)
  • Improve frame label sizing on smaller viewports and when zoomed out. (#7746)
  • Add image pipeline starter template with visual node-based AI image generation workflows. (#7863)
  • Restructure agent starter template with manager-based architecture and mode system. (#7640)

Bug fixes

  • Fix geo shapes with text not being resizable to a smaller size. (#7878)
  • Fix shapes exploding when resizing unaligned arrows. (#7855)
  • Fix Safari pinch zoom resetting selection to previous shapes. (#7777)
  • Fix toggle-lock action firing when no shapes are selected. (#7815)
  • Fix menu items (zoom to selection, cut, copy, delete) being enabled when not in select tool. (#7811)
  • Fix excessive tab indentation in text shapes (now 2 spaces instead of 8). (#7796)
  • Fix canvas-in-front elements (cursors, following indicators) appearing in front of UI panels instead of behind them. (#7865)
  • Fix license console message colors for warnings and errors. (#7850)
  • Fix asset resolution being delayed by 500ms when updating an image shape's asset. (#7612)
  • Fix Durable Object SQLite migration in the multiplayer Cloudflare template. (#7829, #7832, #7834, #7835)
  • Fix zoomToFit and getCurrentPageBounds to ignore hidden shapes when computing bounds. (#7770)
  • Fix collaborator shape indicators not rendering after the canvas indicator change. (#7759)
  • Fix rich text content not updating correctly with message squashing due to reference comparison. (#7758)
  • Fix keyboard shortcut menu item labels to use consistent ellipsis formatting. (#7757)
  • Fix spatial index not removing shapes when moved to a different page. (#7700)
  • Fix tldraw failing to load in CJS environments (tsx, ts-node, Jest) due to ESM-only rbush dependency. (#7905)
  • Fix shapes pasted with Ctrl+V not being parented to a frame when they land inside one. (#7938)
  • Fix crash when cropping custom shapes that don't include isCircle in their crop schema. (#7931)
  • Fix draw shapes not rendering correctly on tablets that report zero pen pressure. (#5693)
Prev
v4.2.0
Next
v4.3.0