Back to Tldraw

Instance state

apps/docs/content/sdk-features/instance-state.mdx

5.4.011.7 KB
Original Source

Instance state is the per-tab state that tracks your current session. It includes which page you're viewing, whether the grid is visible, if debug mode is on, and transient interaction state like the current cursor.

Unlike document state (shapes, pages, assets), instance state belongs to a single browser tab and isn't synced between collaborators.

tsx
import { Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'

export default function App() {
	return (
		<div style={{ position: 'fixed', inset: 0 }}>
			<Tldraw
				onMount={(editor) => {
					// Enable grid mode and debug mode
					editor.updateInstanceState({
						isGridMode: true,
						isDebugMode: true,
					})
				}}
			/>
		</div>
	)
}

Reading instance state

Use Editor#getInstanceState to access the current instance state. The returned object contains all session properties:

typescript
const instance = editor.getInstanceState()

// Check modes
instance.isGridMode // true if grid is visible
instance.isDebugMode // true if debug overlays are shown
instance.isFocusMode // true if UI is minimized
instance.isPenMode // true once a direct-display stylus (e.g. Apple Pencil) is used
instance.isToolLocked // true if tool stays active after creating shapes
instance.isReadonly // true if editing is disabled

// Current state
instance.currentPageId // which page is active
instance.cursor // cursor type and rotation
instance.isFocused // whether the editor has focus

// Screen information
instance.screenBounds // viewport dimensions
instance.devicePixelRatio // display scaling factor
instance.isCoarsePointer // true for touch input

The result is reactive. When you access it inside a track component or react callback, your code re-runs when the state changes:

tsx
import { track, useEditor } from 'tldraw'

const DebugIndicator = track(function DebugIndicator() {
	const editor = useEditor()
	const { isDebugMode, isGridMode } = editor.getInstanceState()

	return (
		<div>
			Debug: {isDebugMode ? 'on' : 'off'}, Grid: {isGridMode ? 'on' : 'off'}
		</div>
	)
})

Updating instance state

Use Editor#updateInstanceState to modify instance properties:

typescript
// Enable grid mode
editor.updateInstanceState({ isGridMode: true })

// Enable multiple options at once
editor.updateInstanceState({
	isDebugMode: true,
	isToolLocked: true,
})

// Toggle a mode
const { isGridMode } = editor.getInstanceState()
editor.updateInstanceState({ isGridMode: !isGridMode })

Changes take effect immediately. By default they are not added to the undo stack; pass { history: 'record' } as the second argument to record them. When using a persistenceKey, some properties persist across sessions (see Session persistence below).

Note that currentPageId cannot be changed through updateInstanceState. Use Editor#setCurrentPage instead.

Available properties

Mode flags

These boolean flags control major editor behaviors:

PropertyDescriptionPersists
isGridModeShow grid overlay on the canvasYes
isDebugModeShow the debug panel (current tool state, debug menu)Yes
isFocusModeMinimize the UI to just the canvasYes
isToolLockedKeep current tool active after creating a shapeYes
isReadonlyPrevent document edits, except for shapes whose util allows editing in readonlyNo
isPenModeSet automatically when a direct-display stylus (e.g. Apple Pencil) is used; touch is ignored while onNo
isFocusedWhether the editor currently has keyboard focusNo

Display state

Properties that track the current display environment:

PropertyDescriptionPersists
screenBoundsViewport position and dimensions (x, y, w, h)No
devicePixelRatioDisplay scaling factor (e.g., 2 for Retina)No
isCoarsePointerIndicates touch or low-precision input is active; mirrors tlenvReactiveNo
isHoveringCanvasWhether pointer is over the canvas (null if no hover support)No
insetsWhether the container is inset from the document edge on each side, [top, right, bottom, left]No
PropertyDescriptionPersists
currentPageIdID of the currently active pageYes
openMenusUnused by the SDK; open menus are tracked by tlmenusNo

Interaction state

Temporary state used during user interactions:

PropertyDescriptionPersists
cursorCurrent cursor type and rotationNo
brushSelection brush bounds during drag selectionNo
zoomBrushZoom brush bounds during zoom-to-areaNo
scribblesActive scribble animations (eraser trails, etc.)No
isChangingStyleSet after a style change; resets after 1s or on pointer moveNo
cameraStateWhether the camera is 'idle' or 'moving'No

Shape creation

Settings that affect newly created shapes:

PropertyDescriptionPersists
opacityForNextShapeOpacity applied to new shapes (0-1)No
stylesForNextShapeStyle values applied to new shapesNo
duplicatePropsState for smart duplicate offsetNo

Collaboration

Properties for multiplayer features:

PropertyDescriptionPersists
followingUserIdID of user being followed, or nullNo
highlightedUserIdsIDs of users whose cursors are highlightedNo
chatMessageCurrent chat message being composedNo
isChattingWhether chat input is activeNo

Custom data

PropertyDescriptionPersists
metaArbitrary JSON data for your applicationNo
exportBackgroundInclude background color when exportingYes

The "Persists" column shows what a persistenceKey saves between sessions. Separately, when you call loadSnapshot, a wider set of instance properties (screen bounds, pointer type, readonly, and the mode flags above) is preserved from the existing store rather than overwritten by the snapshot; see TLSessionStateSnapshot.

Common patterns

Focus mode

Focus mode hides most of the UI for presentation or distraction-free editing:

typescript
editor.updateInstanceState({ isFocusMode: true })

Grid mode

Show a grid overlay to help with alignment:

typescript
editor.updateInstanceState({ isGridMode: true })

The grid respects the camera zoom level. You can customize the grid appearance by overriding the Grid component via the components prop.

Debug mode

Debug mode shows the debug panel: the current tool state path, an optional FPS counter, and the debug menu:

typescript
editor.updateInstanceState({ isDebugMode: true })

Tool lock

When tool lock is enabled, the current tool stays active after creating a shape instead of returning to the select tool:

typescript
editor.updateInstanceState({ isToolLocked: true })

Custom tools should check this property to decide whether to return to select after completing their action. See Tools for implementation details.

Cursor customization

Set a custom cursor type and rotation:

typescript
// Use the setCursor helper
editor.setCursor({ type: 'cross', rotation: 0 })

// Or update directly
editor.updateInstanceState({
	cursor: { type: 'grab', rotation: 0 },
})

See Cursors for the full list of cursor types and rotation handling.

Detecting touch input

Check isCoarsePointer to adapt your UI for touch:

tsx
const { isCoarsePointer } = editor.getInstanceState()
const buttonSize = isCoarsePointer ? 48 : 32 // Larger touch targets

The value updates automatically when the user switches between mouse and touch.

Session persistence

When you use a persistenceKey on the Tldraw component, some instance properties persist across browser sessions. Properties marked "Persists: Yes" in the tables above are saved and restored, along with each page's camera and selection.

Temporary state like cursor position, selection brushes, and scribbles always resets when the page reloads. currentPageId is restored only if that page still exists in the document.

Instance state vs page state

Instance state (TLInstance) is global to the editor: there's one instance record per browser tab. Page state (TLInstancePageState) is per-page and tracks selection, hover, editing, cropping, and focused group for that page. See Selection.

typescript
// Instance state: global to the editor
const instance = editor.getInstanceState()
instance.currentPageId // which page is active
instance.isGridMode // applies to all pages

// Page state: specific to the current page
const pageState = editor.getCurrentPageState()
pageState.selectedShapeIds // selection on this page
pageState.editingShapeId // shape being edited on this page
pageState.hoveredShapeId // shape under cursor on this page

When you switch pages, the instance's currentPageId changes, but each page retains its own selection and editing state.

  • Cursors — Cursor types and rotation handling
  • Readonly mode — Disable editing with isReadonly
  • Focus — Editor focus state with isFocused
  • Tools — Tool lock behavior with isToolLocked
  • User preferences — Global preferences that persist across editor instances
  • Focus mode — Enable focus mode to minimize the UI.
  • Custom grid — Use isGridMode with a custom grid component.
  • Read-only — Disable editing with isReadonly.