Skip to content

Releases: tldraw/tldraw

v4.5.6

01 Apr 10:10

Choose a tag to compare

Generated from commits between v4.5.6 and HEAD

v4.5.5

01 Apr 09:22

Choose a tag to compare

Generated from commits between v4.5.5 and HEAD

v4.5.4

26 Mar 10:28

Choose a tag to compare

Generated from commits between v4.5.4 and HEAD

v4.5.3

18 Mar 15:57

Choose a tag to compare

Generated from commits between v4.5.3 and HEAD

v4.5.2

18 Mar 12:00

Choose a tag to compare

Generated from commits between v4.5.2 and HEAD

v4.5.1

18 Mar 11:28

Choose a tag to compare

Generated from commits between v4.5.1 and HEAD

v4.5.0

18 Mar 11:05

Choose a tag to compare

This release adds click-through on transparent image pixels, configurable embed definitions via EmbedShapeUtil.configure(), correct high-DPI image sizing across platforms, and a new Editor.resizeToBounds() method. It also includes SVG sanitization for external content, TypeScript enum-to-const refactoring for Node strip-types compatibility, and various other improvements and bug fixes.

What's new

Click-through on transparent image pixels (#7942)

Clicking on transparent areas of PNG, WebP, GIF, and AVIF images now selects shapes behind the image instead of the image itself. This works with cropped, flipped, and circle-cropped images.

This is powered by a new Geometry2d.ignoreHit(point) method that allows geometries to reject successful hit tests.

🔜 Configurable embed definitions (#8034)

Embed definitions are now configured through EmbedShapeUtil.configure() instead of the static setEmbedDefinitions() method, which has been deprecated. The embeds prop on Tldraw is deprecated in favor of this approach.

import { EmbedShapeUtil, DEFAULT_EMBED_DEFINITIONS } from 'tldraw'

const shapeUtils = [
	EmbedShapeUtil.configure({
		embedDefinitions: [...DEFAULT_EMBED_DEFINITIONS, myCustomEmbed],
	}),
]
Migration guide

Before:

EmbedShapeUtil.setEmbedDefinitions([...DEFAULT_EMBED_DEFINITIONS, myEmbed])

After:

const shapeUtils = [
	EmbedShapeUtil.configure({
		embedDefinitions: [...DEFAULT_EMBED_DEFINITIONS, myEmbed],
	}),
]

The embeds prop on Tldraw still works but is deprecated. Use EmbedShapeUtil.configure() instead.

API changes

  • 🔜 EmbedShapeUtil.setEmbedDefinitions() deprecated. Use EmbedShapeUtil.configure({ embedDefinitions: [...] }) instead. (#8034)

  • 🔜 Tldraw embeds prop is deprecated. Configure embed definitions via EmbedShapeUtil.configure(). (#8034)

  • Add Geometry2d.ignoreHit(point) for rejecting hit tests on transparent pixels. (#7942)

  • Add Editor.resizeToBounds(shapes, bounds) for resizing shapes to fit target bounds. (#8120)

  • Add Editor.getResizeScaleFactor() for computing the dynamic size scale factor at the current zoom level. (#8042)

  • Add sanitizeSvg(svgText: string) export for sanitizing SVG content against XSS and data exfiltration. (#7896)

  • Add experimental experimental__onDropOnCanvas option to intercept canvas drop events. Return true from the callback to prevent the editor's default drop behavior. (#7911)

    <Tldraw
    	options={{
    		experimental__onDropOnCanvas: (point, event) => {
    			// Handle drop at page-space point
    			return true // prevent default
    		},
    	}}
    />
  • Add optional pixelRatio property to TLImageAsset for correct high-DPI image sizing. Detected automatically from PNG metadata. (#8163)

  • Replace TypeScript enums (MigrationFailureReason, PORTRAIT_BREAKPOINT) with const object + type alias pattern for compatibility with Node's built-in TypeScript support (strip-types). Values are unchanged. (#8084)

Improvements

  • Improve arrow component rendering performance with finer-grained reactivity. (#8167)
  • Simplify paste-parent selection to use canReceiveNewChildrenOfType instead of frame-specific checks. Prevent edge-only overlap from auto-reparenting pasted shapes into adjacent frames. (#8057)
  • Save link and alt-text values when clicking outside the editor instead of discarding changes. (#8037)
  • Add SVG sanitization on paste and file drop to prevent XSS and data exfiltration. (#7896)
  • Fix circular dependencies across @tldraw/state, @tldraw/editor, and @tldraw/tldraw packages to improve compatibility with Jest mocking and tree-shaking. (#7935)
  • Fix high-DPI image sizing to work correctly across macOS and Windows by detecting the source DPI baseline from PNG metadata. (#8163)

Bug fixes

  • Fix shapes pasted with Ctrl+V not being parented to a frame when they land inside one. (#7938)
  • Fix a crash when cropping custom shapes that don't include isCircle in their crop schema. (#7931)
  • Fix a crash when loading session state without a currentPageId (e.g. when using deep links). (#7994)
  • Fix U+2028/U+2029 line separators breaking chunked sync messages. (#7918)
  • Fix "Download original" not triggering a download for cross-origin assets. (#8090)
  • Fix arrow endpoints terminating at invisible clipped shape boundaries instead of at the frame edge. (#7932)
  • Fix sticky notes having a hard shadow instead of a soft drop shadow when exported as SVG. (#7934)
  • Fix rich text toolbar staying open when the editing shape is deleted by another user. (#8050)
  • Fix SVG sanitizer stripping embedded SVG data URIs on <image> elements. Nested SVGs are now recursively sanitized instead of blocked. (#8087)
  • Fix TldrawSelectionForeground crashing when used without TldrawUiContextProvider. (#8011)
  • Fix dynamic-size shapes losing shadows and dashes too early when zoomed out. (#8040)
  • Fix a crash when resizing draw or highlight shapes to zero width or height. (#8067)
  • Fix crash when enabling Debug SVG with shapes on the canvas. (#8101)
  • Fix localOffset mutation bug in stretchShapes when shapes have parent transforms. (#8120)
  • Fix arrow SVG export producing invalid negative <foreignObject> dimensions when arrows have no text label. (#8137)
  • Fix arrow endpoint flickering when anchors are at exact shape boundaries. (#8130)
  • Fix shapes dragged from the toolbar not respecting dynamic size mode. (#8042)
  • Fix dragging unselected shapes that have an onClick handler. (#7936)
  • Fix false positive "multiple instances" warning in Next.js dev mode. (#7933)
  • Fix missing alt text on rendered image shapes in some cases. (#8158)
  • Fix drawing on tablets that report zero pen pressure. (#5693)
  • Fix create-tldraw CLI to always create a subdirectory from the project name. (#8161)

v4.4.1

09 Mar 10:01

Choose a tag to compare

Generated from commits between v4.4.1 and HEAD

v4.4.0

18 Feb 12:05

Choose a tag to compare

This release adds a consolidated options prop, quick zoom navigation, a fill styles dropdown, a new TldrawUiSelect component, and shape-aware binding checks. 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

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, and marking useLegactIndicator to return false:

class MyShapeUtil extends ShapeUtil<MyShape> {
	getIndicatorPath(shape: MyShape): TLIndicatorPath | undefined {
		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
	}
}

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.

User preference: 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.

Performance improvements (#7676, #7826, #7840, #7657)

This release includes several performance improvements for large canvases:

  • R-tree spatial indexing: Shape queries now use an R-tree (RBush) for O(log n) lookups instead of O(n) iteration, significantly improving brushing, scribble selection, and erasing with many shapes on the canvas. The spatial index is maintained internally and accessed through existing methods like editor.getShapesAtPoint() and editor.getShapeAtPoint().
  • Faster panning: Hover hit-testing is now skipped during camera movement, reducing work while panning through large documents.
  • Reduced allocations: Optimized Set comparisons, reduced memory allocations, and added string hash caching for better performance in large canvases and multiplayer rooms.
  • Smarter network scheduling: Solo-mode network traffic is reduced by throttling to 1 FPS when no collaborators are present.

Re-designed 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.

This is powered by a new generic session system on ScribbleManager. Sessions group multiple scribbles together and control how they fade:

// Start a grouped session (used internally by the laser tool)
const sessionId = editor.scribbles.startSession({
	fadeMode: 'grouped',
	idleTimeoutMs: 1200,
	fadeDurationMs: 500,
})

// Add scribbles and points to a session
editor.scribbles.addScribbleToSession(sessionId, { color: 'laser' })
editor.scribbles.addPointToSession(sessionId, scribbleId, x, y)

// Stop or clear a session
editor.scribbles.stopSession(sessionId)
editor.scribbles.clearSession(sessionId)

New option in TldrawOptions:

  • laserFadeoutMs (default: 500ms) - How long to fade all laser scribbles after the session ends

Image pipeline starter template (#7863)

A new "Image pipeline" starter template is available via npx create-tldraw. It provides a visual node-based canvas for building AI image generation workflows, with custom node shapes, typed port connections, pipeline regions with play/stop controls, and a DAG-based execution engine backed by a Cloudflare Worker API.

Agent starter template improvements (#7640)

The agent starter template has been restructured around a manager-based architecture for better modularity and extensibility. It now includes a mode system for controlling agent capabilities per mode, action schema registries, prompt part definitions, canvas linting, and user action tracking. The template also renames "SimpleShape" to "FocusedShape" for clarity.

TldrawUiSelect component (#7566)

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

🔜 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.

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 scribble session system: ScribbleManager.startSession(), stopSession(), clearSession(), extendSession(), isSessionActive(), addScribbleToSession(), addPointToSession(), and ScribbleSessionOptions type. Add LaserTool.getSessionId(). Add laserFadeoutMs to TldrawOptions. (#7681)
  • Add FpsScheduler class to create FPS-throttled function queues with configurable target rates. (#7418)

Improvements

Read more

v4.3.2

14 Feb 22:12

Choose a tag to compare

Generated from commits between v4.3.2 and HEAD