diff --git a/apps/examples/src/examples/state-store/README.md b/apps/examples/src/examples/state-store/README.md
new file mode 100644
index 000000000..6ccf19926
--- /dev/null
+++ b/apps/examples/src/examples/state-store/README.md
@@ -0,0 +1,12 @@
+---
+title: State and store
+component: ./StateStoreExample.tsx
+category: basic
+priority: 3
+keywords: [signia, state, store, side, effects, subscribe, track]
+---
+
+Tldraw uses signals to manage its state and store. You can subscribe to
+values in the store and run side effects when they change.
+
+---
diff --git a/apps/examples/src/examples/state-store/StateStoreExample.tsx b/apps/examples/src/examples/state-store/StateStoreExample.tsx
new file mode 100644
index 000000000..fa3516f18
--- /dev/null
+++ b/apps/examples/src/examples/state-store/StateStoreExample.tsx
@@ -0,0 +1,88 @@
+import { TLComponents, Tldraw, track, useEditor, useReactor, useValue } from 'tldraw'
+import 'tldraw/tldraw.css'
+
+// [1]
+const InfoPanel = track(() => {
+ const editor = useEditor()
+ const tool = editor.getCurrentToolId()
+ const zoom = editor.getZoomLevel().toFixed(2)
+ useReactor(
+ 'change title',
+ () => {
+ const shapes = editor.getCurrentPageShapes()
+ document.title = `shapes: ${shapes.length}`
+ },
+ [editor]
+ )
+ return (
+
+
tool: {tool}
+
zoom: {zoom}
+
+ )
+})
+
+// [2]
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function AlternativeInfoPanel() {
+ const editor = useEditor()
+ const tool = useValue(
+ 'current tool',
+ () => {
+ if (!editor) throw new Error('No editor')
+ return `Current Tool: ${editor.getCurrentToolId()}`
+ },
+ [editor]
+ )
+ const zoom = useValue(
+ 'zoom',
+ () => {
+ if (!editor) throw new Error('No editor')
+ return `Zoom Level: ${editor.getZoomLevel().toFixed(2)}`
+ },
+ [editor]
+ )
+
+ return (
+
+ )
+}
+
+const components: TLComponents = {
+ SharePanel: InfoPanel,
+}
+
+export default function StateStoreExample() {
+ return (
+
+
+
+ )
+}
+
+/*
+
+Tldraw uses signals to manage its state and store. You can subscribe to values in the store
+and run side effects when they change.
+
+[1]
+ Our InfoPanel component will display above the style panel. We want it to show the current
+ selected tool and zoom level of the editor. In order to make sure it displays up-to-date
+ information, we can wrap the component in the track function. This will track any signals
+ used in the component and re-render it when they change.
+
+ We also use the useReactor hook to update the document title with the number of shapes. This
+ side effect will run whenever the shapes on the page change. We pass the editor as a
+ dependency to the useReactor hook so it will always have the latest editor instance.
+ useQuickReactor runs immediately, whereas useReactor runs on the next animation frame.
+
+ Check out the Fog of War example to see useReactor in action.
+
+[2]
+ We can also use the useValue hook to subscribe to a value in the store. You can pass it a
+ value or a function. Functions will be memoized and only re-run when the dependencies change.
+
+*/