# @tldraw/state

> tldraw infinite canvas SDK (state).

Latest version **5.4.2** (published 2026-09-10) · MIT license · 316.0K weekly downloads

## Install

```sh
npm install @tldraw/state
pnpm add @tldraw/state
yarn add @tldraw/state
bun add @tldraw/state
```

## Health

**Score 90/100 (A)** — status: active.

Positive: moderate downloads; esm support; no vulnerabilities; has provenance; recently updated; high maintenance score; popular repo; extremely popular.

Warnings: no types.

## Facts

| | |
|---|---|
| Version | 5.4.2 |
| Published | 2026-09-10 |
| First published | 2023-06-20 |
| Weekly downloads | 316.0K |
| License | MIT |
| TypeScript types | none |
| Module format | ESM + CommonJS |
| Node | >=22.12.0 |
| Dependencies | 1 |
| Unpacked size | 610.9 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 50341 |
| Author | tldraw Inc. |
| Maintainers | tldraw-personal, steveruizok, mimetldraw, mitjatldraw, siobhantldraw |
| Keywords | tldraw, sdk, drawing, app, development, whiteboard, canvas, infinite |

## Links

- npm: https://www.npmjs.com/package/@tldraw/state
- Repository: https://github.com/tldraw/tldraw
- Homepage: https://tldraw.dev
- Issues: https://github.com/tldraw/tldraw/issues
- npm.io page: https://npm.io/package/@tldraw/state

## Dependencies (1)

- [@tldraw/utils](https://npm.io/package/@tldraw/utils.md) 5.4.2

## Alternatives

- [exif-parser](https://npm.io/package/exif-parser.md) — 3.8M weekly downloads
- [vite-plugin-compression](https://npm.io/package/vite-plugin-compression.md) — 569.5K weekly downloads
- [pica](https://npm.io/package/pica.md) — 442.4K weekly downloads
- [@reportportal/client-javascript](https://npm.io/package/@reportportal/client-javascript.md) — 408.8K weekly downloads
- [ionic-img-viewer](https://npm.io/package/ionic-img-viewer.md) — 448 weekly downloads

## Recent versions

- 5.4.2 (latest) — 2026-09-10
- 5.5.0-canary.9315ab45e518 (canary) — 2026-09-16
- 5.5.0-next.b6a9f7cd7be2 (next) — 2026-09-16
- 5.3.0-internal.1640468db8fd (internal) — 2026-07-23
- 4.5.12 (revision) — 2026-05-13
- 2.0.0-canary.3b92faa5cf0a (beta) — 2024-02-29
- 2.0.0-alpha.19 (alpha) — 2023-12-12
- 5.5.0-canary.3b72bdfdfda2 — 2026-09-16
- 5.5.0-canary.bff0301ca1f4 — 2026-09-16
- 5.5.0-canary.4e8434557adc — 2026-09-16
- 5.5.0-canary.5176de035caa — 2026-09-16
- 5.5.0-canary.382e4133bc69 — 2026-09-16
- 5.5.0-canary.25c45508bd83 — 2026-09-16
- 5.5.0-canary.acfffd7c8848 — 2026-09-16
- 5.5.0-canary.e7e4b3c8d9b7 — 2026-09-16
- … 4460 more at https://npm.io/package/@tldraw/state/versions

## README

# @tldraw/state

`@tldraw/state` is a powerful and lightweight TypeScript library for managing reactive state using signals. It provides fine-grained reactive primitives that automatically track dependencies and efficiently update only what needs to change.

`@tldraw/state` powers the reactive system at the heart of [tldraw](https://www.tldraw.com), handling everything from canvas updates to collaborative state synchronization. It's designed to work seamlessly with [@tldraw/store](https://github.com/tldraw/tldraw/tree/main/packages/store) and has optional [React bindings](https://github.com/tldraw/tldraw/tree/main/packages/state-react).

## Documentation

Documentation for the most recent release can be found on [tldraw.dev/docs](https://tldraw.dev/docs), including [reference docs](https://tldraw.dev/reference/editor/Editor). Our release notes can be found [here](https://tldraw.dev/releases).

For more agent-friendly docs, see our [LLMs.txt](https://tldraw.dev/llms.txt).

A `DOCS.md` file is included alongside this README in the published package, with detailed API documentation and usage examples.

## Why @tldraw/state?

- **Fine-grained reactivity** - Only re-runs computations when their actual dependencies change
- **High performance** - Lazy evaluation and efficient dependency tracking
- **Automatic updates** - Derived values and side effects update automatically
- **Incremental updates** - Built-in diff history and transactions with rollback support
- **Framework agnostic** - Works with any JavaScript framework or vanilla JS
- **TypeScript first** - Excellent type safety with full TypeScript support

Perfect for building reactive UIs, real-time collaborative apps, and complex state machines where performance and predictability matter.

## Installation

```bash
npm install @tldraw/state
```

## Quick start

```ts
import { atom, computed, react } from '@tldraw/state'

// Create reactive state
const name = atom('name', 'World')
const count = atom('count', 0)

// Derive values automatically
const greeting = computed('greeting', () => {
	return `Hello, ${name.get()}! Count: ${count.get()}`
})

// React to changes
react('logger', () => {
	console.log(greeting.get())
})
// Logs: "Hello, World! Count: 0"

// Update state - reactions run automatically
name.set('tldraw')
// Logs: "Hello, tldraw! Count: 0"

count.set(42)
// Logs: "Hello, tldraw! Count: 42"
```

## Core concepts

### Atoms - state containers

Atoms hold raw values and are the foundation of your reactive state:

```ts
import { atom } from '@tldraw/state'

// Create atoms with initial values
const user = atom('user', { name: 'Alice', age: 30 })
const theme = atom('theme', 'light')

// Read values
console.log(user.get().name) // 'Alice'

// Update values
user.update((current) => ({ ...current, age: 31 }))
theme.set('dark')
```

### Computed values - automatic derivation

Computed signals derive their values from other signals and update automatically:

```ts
import { computed } from '@tldraw/state'

const firstName = atom('firstName', 'John')
const lastName = atom('lastName', 'Doe')

const fullName = computed('fullName', () => {
	return `${firstName.get()} ${lastName.get()}`
})

console.log(fullName.get()) // "John Doe"

firstName.set('Jane')
console.log(fullName.get()) // "Jane Doe" - automatically updated!
```

### Reactions - side effects

Reactions run side effects when their dependencies change:

```ts
import { react } from '@tldraw/state'

const selectedId = atom('selectedId', null)

// Update UI when selection changes
const stop = react('update-selection-ui', () => {
	const id = selectedId.get()
	document.getElementById('selected').textContent = id || 'None'
})

selectedId.set('shape-123')
// UI automatically updates

// Clean up when no longer needed
stop()
```

### Transactions - batched updates

Batch multiple updates to prevent intermediate reactions:

```ts
import { transact } from '@tldraw/state'

const x = atom('x', 0)
const y = atom('y', 0)

const position = computed('position', () => `(${x.get()}, ${y.get()})`)

react('log-position', () => console.log(position.get()))
// Logs: "(0, 0)"

transact(() => {
	x.set(10)
	y.set(20)
	// Reaction runs only once after transaction
})
// Logs: "(10, 20)"
```

## Advanced features

### History and diffs

Track changes over time so that dependents can update incrementally instead of recomputing from scratch:

```ts
const canvas = atom(
	'canvas',
	{ shapes: [] },
	{
		historyLength: 100,
		computeDiff: (prev, next) => ({ prev, next }),
	}
)

// Remember where you are...
const startEpoch = canvas.lastChangedEpoch

// ... make changes ...
canvas.update((state) => ({ shapes: [...state.shapes, newShape] }))

// ... and get the diffs since then (or RESET_VALUE if the history doesn't reach back that far)
const diffs = canvas.getDiffSince(startEpoch)
```

### Performance optimization

Use `unsafe__withoutCapture` to read values without creating dependencies:

```ts
const expensiveComputed = computed('expensive', () => {
	const important = importantValue.get()

	// Read this without making it a dependency
	const metadata = unsafe__withoutCapture(() => metadataAtom.get())

	return computeExpensiveValue(important, metadata)
})
```

### Debugging

Use `whyAmIRunning()` to understand what triggered an update:

```ts
react('debug-reaction', () => {
	whyAmIRunning() // Logs dependency tree to console
	// Your reaction code...
})
```

## Integration examples

### With tldraw SDK

```ts
// e.g. in Tldraw's onMount callback, which receives the editor
const selectedShapes = computed('selectedShapes', () => {
	return editor.getSelectedShapeIds().map((id) => editor.getShape(id))
})

// React to selection changes; call the returned function to stop
const stop = react('update-property-panel', () => {
	const shapes = selectedShapes.get()
	updatePropertyPanel(shapes)
})
```

### With React

Install the React bindings:

```bash
npm install @tldraw/state-react
```

```tsx
import { track, useAtom, useComputed } from '@tldraw/state-react'

// track() re-renders the component when any signal it reads changes
const Counter = track(function Counter() {
	const count = useAtom('count', 0)
	const doubled = useComputed('doubled', () => count.get() * 2, [count])

	return (
		<div>
			<p>Count: {count.get()}</p>
			<p>Doubled: {doubled.get()}</p>
			<button onClick={() => count.set(count.get() + 1)}>+</button>
		</div>
	)
})
```

## API reference

For complete API documentation, see [DOCS.md](./DOCS.md).

### Core functions

- `atom(name, initialValue, options?)` - Create a reactive state container
- `computed(name, computeFn, options?)` - Create a derived value
- `react(name, effectFn, options?)` - Create a side effect
- `transact(fn)` - Batch state updates

### Class-based APIs

- `@computed` - Decorator for computed class properties

### Advanced

- `reactor(name, effectFn)` - Create a controllable reaction
- `unsafe__withoutCapture(fn)` - Read state without creating dependencies
- `whyAmIRunning()` - Debug what triggered an update
- `getComputedInstance(obj, prop)` - Get underlying computed instance
- `signal.getDiffSince(epoch)` - Get the diffs recorded since an epoch (see `signal.lastChangedEpoch`)

## Related packages

- **[@tldraw/state-react](../state-react)** - React bindings for @tldraw/state
- **[@tldraw/store](../store)** - Record storage built on @tldraw/state
- **[@tldraw/editor](../editor)** - The tldraw canvas editor
- **[@tldraw/tldraw](../tldraw)** - Complete tldraw UI components

## Examples & patterns

Looking for more examples? Check out:

- [tldraw SDK examples](https://github.com/tldraw/tldraw/tree/main/apps/examples) - Real-world usage in tldraw applications

## Contributing

Found a bug? Please [submit an issue](https://github.com/tldraw/tldraw/issues/new).

## License

This project is licensed under the MIT License found [here](https://github.com/tldraw/tldraw/blob/main/packages/state/LICENSE.md). The tldraw SDK is provided under the [tldraw license](https://github.com/tldraw/tldraw/blob/main/LICENSE.md).

## Trademarks

Copyright (c) 2024-present tldraw Inc. The tldraw name and logo are trademarks of tldraw. Please see our [trademark guidelines](https://github.com/tldraw/tldraw/blob/main/TRADEMARKS.md) for info on acceptable usage.

## Contact

Find us on Twitter/X at [@tldraw](https://twitter.com/tldraw). You can contact us by email at [hello@tldraw.com](mailto:hello@tldraw.com).

## Community

Have questions, comments or feedback? [Join our discord](https://discord.tldraw.com/?utm_source=github&utm_medium=readme&utm_campaign=sociallink). For the latest news and release notes, visit [tldraw.dev](https://tldraw.dev).

---
_Source: https://npm.io/package/@tldraw/state · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
