v4.0.0
Welcome to the notes for the 4.0 release of the tldraw SDK. Like our previous major releases, this release includes changes to our license as well as new resources for developers building infinite canvas apps and features with tldraw.
What's new
npm create tldraw
This release includes our new CLI tool, available at npm create tldraw
. You can use this tool to quickly create new tldraw projects using our templates and new starter kits.
Starter kits
This release includes four new starter kits:
- agent is a Cursor-style chatbot starter. This kit replaces our AI module and AI template. If you're interested in driving AI interactions on the canvas, this is a great place to start hacking.
- workflow is a React-flow style starter for node-and-wire applications. If you have an idea for a patch programming interface, an asynchronous workflow tool, or a ComfyUI-style pipeline for images or data, start with this one.
- branching chat is a starter kit for branching AI chats, inspired by tweets by Max Lee and Jacob Colling. Run with it!
- chat is a starter kit where the tldraw canvas is used to create and annotate images. If you're working on an app that features a chatbot (who isn't?) then give this one a spin and try out some ideas.
- multiplayer is clean starter kit featuring our tldraw sync multiplayer backend. Build a multiplayer whiteboard, game, or bypass your school's chat app restrictions.
The starter kits are all MIT licensed, so go ahead and build with them as you like. Learn more on the tldraw docs or get started by running npm create tldraw
in your terminal.
Licensing changes
Like our previous major releases, this release includes changes to our license. Fate and capital both demand that tldraw be sustainable, so these changes are designed to help us commercialize the SDK without cutting off community adoption.
In this release, the SDK is only permitted to be used in development environments unless you have either a trial license, a commercial license, or a hobby license. These licenses come with a license key. The SDK will not work in production unless it has a valid license key.
If you are only using tldraw in localhost or development environments, then you do not need a license. If you wish to use the tldraw SDK in production, you can get a free 100-day trial license.
To use the tldraw SDK in production after the trial ends, commercial projects will require a commercial license and non-commercial projects can apply for a hobby license.
You can read more about our licensing here. If you have any questions, please visit our Discord to chat with the team.
Features and improvements
Drag out of toolbar (#4793)
You can now drag shapes out from the toolbar. It's easy to add this sort of interaction to your own user interfaces, too.
Accessibility: WCAG 2.2 level AA compliance
We've worked over the last months to improve the tldraw SDK's accessibility.
With this release, and with the help of Sarah Fossheim, we were able to complete enough improvements, features, and fixes to become compliant with the WCAG 2.2 AA accessibility standard. We will soon be publicly providing our VPAT compliance document which will provide further details.
- Added an Accessibility Mode that includes labels for the style panel, higher contrast selection rings, quicker tooltips, and several other features intended to improve accessibility (#6444).
- Added new keyboard shortcuts for rotation,
Shift + <
andShift + >
withAlt
for fine adjustments. (#6447) - Fixed missing aria-labels in various places. (#6721) (#6750)
- Made menus scrollable when zoomed in. (#6740)
- Added contrast on our placeholder text and improved menu localization. (#6750)
- Made the image/video toolbars accessible by keyboard shortcut. (#6750)
Custom shapes can clip their children (#6649)
Before this PR, only the built-in frame shape could clip its children. Now, any custom shape can clip its children.
Vertical toolbar (#6532)
It's now easy to make tldraw's toolbar vertical. Override the Toolbar
component, and set orientation
to vertical
:
`<Tldraw
components={{
Toolbar: () =>` <DefaultToolbar orientation="vertical" />,
}}
/>
You can also now use <TldrawUiMenuGroup />
to group together toolbar items.
Breaking changes
- All of tldraw's CSS variables now start with
--tl-
. If you're using any of our variables in your own css, you'll need to update your CSS. For example:--color-background
is now--tl-color-background
--space-4
is now--tl-space-4
--tl-zoom
is still--tl-zoom
(#6568)
Geometry2D
'sisLabel
config option no longer excludes the geometry from the shape bounds calculations by default. A new separate flagexcludeFromShapeBounds
has been added for this. (#6637)- Change the
open-url
event data - rename it fromurl
todestinationUrl
asurl
might shadow some automatically sent properties by analytics tools. (#6654) InFrontOfTheCanvas
now renders as a child of.tl-canvas
, in a.tl-canvas__in-front
wrapper.- The
DefaultColorThemePalette
color theme object has been flattened. If you're using this object, you'll need to update to the new structure, using thegetColorValue
helper. (#6466) - Arrow shapes now have a
richText
instead of atext
prop. The data will migrate automatically, but if you're accessing or setting thearrow.props.text
property directly, you'll need to update your code to use the newarrow.props.richText
property. (#6325) - We removed the
@tldraw/ai
module in favour of the new agent starter kit, which provides a more flexible and customizable API for building AI-powered canvas applications. To upgrade, we recommend cloning or copying relevant code from the agent starter kit, then usinguseTldrawAgent
.
const agent = useTldrawAgent(editor)
agent.prompt('Draw a flowchart for how to update a web package.')
- We no longer swallow most events that happen on the canvas, & we removed
stopEventPropagation
. Most places that used it should be replaced witheditor.markEventAsHandled
which causes tldraw to ignore an event, but allows it to propagate out above tldraw. If you definitely still want stop propagation semantics, you can callevent.stopPropagation()
instead. (#6733) - The style panel has been rewritten to be much easier to customize - similarly to the Toolbar. We removed the several old components (
ArrowheadStylePickerSet
,CommonStylePickerSet
,GeoStylePickerSet
,OpacitySlider
,SplineStylePickerSet
,TextStylePickerSet
,TldrawUiButtonPicker
), and replaced them with newStylePanel*Picker
components. (#6672) - The
tlui-buttons__horizontal
andtlui-buttons__grid
class names have been replaced bytlui-row
andtlui-grid
. (#6526) - The
tlui-toolbar
classnames have been renamed totlui-main-toolbar
- We removed a number of deprecated APIs:
Store.getSnapshot()
andStore.loadSnapshot()
methods → use the same methods oneditor
.RecordType.createCustomId()
method → useRecordType.createId()
Editor.mark()
method → replaced bymarkHistoryStoppingPoint()
Editor.batch()
method → replaced byEditor.run()
Editor.getSvg()
method → replaced byEditor.getSvgElement()
Editor.getDroppingOverShape()
methodEditor.getOpenMenus()
→ replaced byeditor.menus.getOpenMenus()
Editor.addOpenMenu()
→ replaced byeditor.menus.addOpenMenu()
Editor.deleteOpenMenu()
→ replaced byeditor.menus.deleteOpenMenu()
Editor.clearOpenMenus()
→ replaced byeditor.menus.clearOpenMenus()
Editor.getIsMenuOpen()
→ replaced byeditor.menus.hasAnyOpenMenus()
Editor.environment
property → replaced bytlenv
isShapeHidden
prop/option → replaced bygetShapeVisibility
exportAs()
function overloads (multiple parameter signatures) → consolidated to options objectcopyAs()
function overloads (multiple parameter signatures) → consolidated to options objectexportToBlob()
function → useEditor.toImage
useEditableText
→ replaced byuseEditablePlainText
TextLabel
→ replaced byPlainTextLabel
useAsset
→ replaced byuseImageOrVideoAsset
TLSvgOptions
type alias → replaced byTLSvgExportOptions
Vec.norm()
method → replaced byVec.uni()
Geometry2d.nearestPointOnLineSegment()
methoddebugEnableLicensing()
function
Bug Fixes
- Arrow labels are no longer cut off in image exports. (#6637)
- PNGs with that specify their physical pixel dimensions are now parsed correctly. (#6735)
- tldraw sync's
documentClock
no longer advances even if no document changes had occurred when loading a new snapshot. (#6666) - Complex keyboard shortcuts are now formatted correctly on windows and linux. (#6638)
- Pointer events in editable shapes fire consistently on mobile. (#6628)
- On canvas UI renders in the correct place when the canvas is inset from the tldraw container. (#6626)
- Items marked with
usePassThroughWheelEvents
no longer block wheel events when the editor is not focused. (#6640) - Embeds are now positioned correctly on Chrome when zoom isn't 100%. (#6611)
- Prevent the editor from remounting in a loop if given custom assetUrls. (#6605)
- KeyboardShortcutsDialog override is now respected in more places. (#6571)
- Hollow shapes can now be bound-to by arrows when overlapping a filled shape. (#6525)
onDataChange
andonPresenceChange
are now always passed toTLSyncRoom
. (#6549)- Deleting a page now correctly deletes all the shapes on that page. (#6333)
pointingPreciseTimeout
is now respected everywhere it should be. (#6789)
Improvements
- Hold command or control while erasing to erase only the first shape that you began erasing. Useful for erasing overlapping shapes! (#6554)
- Arrow labels now accept rich text formatting. (#6325)
- The toast shown when uploading a file that's too big now shows the maximum file size. (#6745)
- We switched up how we generate shape indexes to avoid incorrectly formatted indexes. (#6646)
- Migrations with explicit
dependsOn
constraints will now be scheduled as close as possible to the things they depend on. (#6702) - Your tldraw license key will now be automatically pulled from an env var if it's set. We check
TLDRAW_LICENSE_KEY
,NEXT_PUBLIC_TLDRAW_LICENSE_KEY
,REACT_APP_TLDRAW_LICENSE_KEY
,VITE_TLDRAW_LICENSE_KEY
, andPUBLIC_TLDRAW_LICENSE_KEY
. - When exporting an annotated image, we no longer add padding unless the annotations stray over the bounds of the image.
- We now completely skip initial font loading when
maxFontsToLoadBeforeRender
is 0. (#6622) useSync
now only sends presence updates, like mouse and camera positions, when there are more than one unique user in a room. (#6524)- tldraw sync has better server-side performance when dealing with large rooms. (#6488)
API changes
- Added support for sending custom messages from
TLSocketRoom
to connected clients.TLSocketRoom
exposes a new methodsendCustomMessage
to send arbitrary data to a connected client, anduseSync
accepts a new callbackonCustomMessageReceived
to receive it. Special thanks to community contributor Fabian Iwand (@mootari) for this. (#6614) - The
Editor.getShapesAtPoint
can now accept a number or a number tuple for itsmargin
option that will be used as the inner and outer margins. This works best whenhitInside
istrue
; ifhitInside
is false, then the larger of the two margins will be used for both inside and outside margin. (#6525) - Added
ShapeUtil.isExportBoundsContainer
. If this returns true, exports of your shape won't have padding if their bounds contain all other shapes in the export. (#6636) - Added
ShapeUtil.canCull
, which can be used to disable culling on certain shapes. (#6699) RoomSnapshot
now has adocumentClock
field. (#6666)TldrawOptions
now hasuiDragDistanceSquared
anduiCourseDragDistanceSquared
for controlling the dragging threshold of UI components. (#6596)TldrawUiMenuItem
now acceptsonDragFromToolbarToCreateShape
andonDragStart
to facilitate dragging shapes out of a toolbar. (#4793)TldrawUiInput
now acceptsaria-label
. (#6760)TldrawUiMenuCheckboxItem
now acceptslang
. (#6750)- New translation keys for confirming a crop operation (#6663), rhombus 2 (#6653), selected tool states (#6734), max file size notices (#6745).
- Adds
onDragFromToolbarToCreateShape
,OnDragFromToolbarToCreateShapesOpts
, andonDragStart
onTldrawUiMenuItem
to facilitate dragging shapes out of a toolbar. (#4793) TLSocketRoom#getRecord
return type now reflects that it returnsundefined
if the record was not found. (#6488)StoreSchema#migrateStoreSnapshot
has a new optionmutateInputStore: boolean
which is false by default. If left false, it will copy the input store withstructuredClone
. If set to true the mutators will be allowed to mutate the input store. (#6488)- Added the
overlapsPolygon
method to theGeometry2D
class. (#6679) - Added
editor.toImageDataUrl
, which is useful for applications that need to display or work with image data URLs directly instead of blob objects. (#6624) - Expose
notifyIfFileNotAllowed
which is useful when creating custom file upload flows. (#6625) - Added
TldrawUiRow
andTldrawUiGrid
layout classes. (#6526) - Added
EditorAtom
, a helper for storing editor UI state in anAtom
. (#6531) - There's now a
data-state
attribute on.tl-container
which contains the current state tree path. This makes it easier to write styles based on the current interaction state. (#6515) - Add an option to
squashRecordDiffs
to allow mutating the first diff instead of creating a new diff. (#6533) - Add
ShapeWrapper
to thecomponents
prop to allow customizing how every single shape is rendered to the DOM. (#6514) TldrawCropHandles
,ToggleToolLockedButton
,getHitShapeOnCanvasPointerDown
,getAssetInfo
,useUnlockedSelectedShapesCount
, andLockGroup
are now exported publicly. (#6513)- Add
editor.updatePointer()
, which is useful when you want an interaction to update in response to an external change, such as moving the camera or creating a shape. (#6494) - Pass
isCreatingShape
toShapeUtil.onHandleDrag
and other handle drag callbacks. This will be true if the current interaction is creating the shape in question. (#6493) - Add
onHandleDragStart
andonHandleDragEnd
toShapeUtil
to complementonHandleDrag
. (#6489) - Add
onTranslateCancel
,onResizeCancel
,onRotateCancel
, andonHandleDragCancel
callbacks toShapeUtil
. (#6489) - Added optional
opts
parameter toputExternalContent()
replaceExternalContent()
withforce?: boolean
option - Added optional
opts
parameter toreplaceExternalContent(info, opts?)
method withforce?: boolean
option. Both methods now respect readonly state by default, but can bypass it with{ force: true }
(#6729) TldrawUiSlider
sonHistoryMark
prop is now optional. (#6718)clearArrowTargetState
,getArrowTargetState
, andupdateArrowTargetState
are now part of the public API, making it possible to re-implementTldrawOverlays
. (#6788)ArrowShapeUtil.options.shouldBeExact
now acceptsisPrecise
as a second argument. (#6781)
That's it for this release! Breaking changes are minimal