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 + >withAltfor 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-backgroundis now--tl-color-background--space-4is now--tl-space-4--tl-zoomis still--tl-zoom(#6568)
Geometry2D'sisLabelconfig option no longer excludes the geometry from the shape bounds calculations by default. A new separate flagexcludeFromShapeBoundshas been added for this. (#6637)- Change the
open-urlevent data - rename it fromurltodestinationUrlasurlmight shadow some automatically sent properties by analytics tools. (#6654) InFrontOfTheCanvasnow renders as a child of.tl-canvas, in a.tl-canvas__in-frontwrapper.- The
DefaultColorThemePalettecolor theme object has been flattened. If you're using this object, you'll need to update to the new structure, using thegetColorValuehelper. (#6466) - Arrow shapes now have a
richTextinstead of atextprop. The data will migrate automatically, but if you're accessing or setting thearrow.props.textproperty directly, you'll need to update your code to use the newarrow.props.richTextproperty. (#6325) - We removed the
@tldraw/aimodule 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.markEventAsHandledwhich 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*Pickercomponents. (#6672) - The
tlui-buttons__horizontalandtlui-buttons__gridclass names have been replaced bytlui-rowandtlui-grid. (#6526) - The
tlui-toolbarclassnames 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.environmentproperty → replaced bytlenvisShapeHiddenprop/option → replaced bygetShapeVisibilityexportAs()function overloads (multiple parameter signatures) → consolidated to options objectcopyAs()function overloads (multiple parameter signatures) → consolidated to options objectexportToBlob()function → useEditor.toImageuseEditableText→ replaced byuseEditablePlainTextTextLabel→ replaced byPlainTextLabeluseAsset→ replaced byuseImageOrVideoAssetTLSvgOptionstype alias → replaced byTLSvgExportOptionsVec.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
documentClockno 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
usePassThroughWheelEventsno 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)
onDataChangeandonPresenceChangeare now always passed toTLSyncRoom. (#6549)- Deleting a page now correctly deletes all the shapes on that page. (#6333)
pointingPreciseTimeoutis 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
dependsOnconstraints 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
maxFontsToLoadBeforeRenderis 0. (#6622) useSyncnow 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
TLSocketRoomto connected clients.TLSocketRoomexposes a new methodsendCustomMessageto send arbitrary data to a connected client, anduseSyncaccepts a new callbackonCustomMessageReceivedto receive it. Special thanks to community contributor Fabian Iwand (@mootari) for this. (#6614) - The
Editor.getShapesAtPointcan now accept a number or a number tuple for itsmarginoption that will be used as the inner and outer margins. This works best whenhitInsideistrue; ifhitInsideis 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) RoomSnapshotnow has adocumentClockfield. (#6666)TldrawOptionsnow hasuiDragDistanceSquaredanduiCourseDragDistanceSquaredfor controlling the dragging threshold of UI components. (#6596)TldrawUiMenuItemnow acceptsonDragFromToolbarToCreateShapeandonDragStartto facilitate dragging shapes out of a toolbar. (#4793)TldrawUiInputnow acceptsaria-label. (#6760)TldrawUiMenuCheckboxItemnow 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, andonDragStartonTldrawUiMenuItemto facilitate dragging shapes out of a toolbar. (#4793) TLSocketRoom#getRecordreturn type now reflects that it returnsundefinedif the record was not found. (#6488)StoreSchema#migrateStoreSnapshothas a new optionmutateInputStore: booleanwhich 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
overlapsPolygonmethod to theGeometry2Dclass. (#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
notifyIfFileNotAllowedwhich is useful when creating custom file upload flows. (#6625) - Added
TldrawUiRowandTldrawUiGridlayout classes. (#6526) - Added
EditorAtom, a helper for storing editor UI state in anAtom. (#6531) - There's now a
data-stateattribute on.tl-containerwhich contains the current state tree path. This makes it easier to write styles based on the current interaction state. (#6515) - Add an option to
squashRecordDiffsto allow mutating the first diff instead of creating a new diff. (#6533) - Add
ShapeWrapperto thecomponentsprop to allow customizing how every single shape is rendered to the DOM. (#6514) TldrawCropHandles,ToggleToolLockedButton,getHitShapeOnCanvasPointerDown,getAssetInfo,useUnlockedSelectedShapesCount, andLockGroupare 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
isCreatingShapetoShapeUtil.onHandleDragand other handle drag callbacks. This will be true if the current interaction is creating the shape in question. (#6493) - Add
onHandleDragStartandonHandleDragEndtoShapeUtilto complementonHandleDrag. (#6489) - Add
onTranslateCancel,onResizeCancel,onRotateCancel, andonHandleDragCancelcallbacks toShapeUtil. (#6489) - Added optional
optsparameter toputExternalContent()replaceExternalContent()withforce?: booleanoption - Added optional
optsparameter toreplaceExternalContent(info, opts?)method withforce?: booleanoption. Both methods now respect readonly state by default, but can bypass it with{ force: true }(#6729) TldrawUiSlidersonHistoryMarkprop is now optional. (#6718)clearArrowTargetState,getArrowTargetState, andupdateArrowTargetStateare now part of the public API, making it possible to re-implementTldrawOverlays. (#6788)ArrowShapeUtil.options.shouldBeExactnow acceptsisPreciseas a
second argument. (#6781)
That's it for this release! Breaking changes are minimal