The $state Rune
Declaring reactive state
Section titled “Declaring reactive state”$state creates a reactive value. Read it like a normal variable and reassign it like a normal variable — the compiler makes every read a dependency and every write a trigger.
<script> let count = $state(0);</script>
<button onclick={() => count++}>{count}</button>There are no setters and no special update calls. count++ is a real reassignment, and because the compiler tracks that the button’s text reads count, that one text node updates.
Deep reactivity via proxies
Section titled “Deep reactivity via proxies”When you pass an object or array to $state, Svelte wraps it in a proxy so that mutations — not just reassignments — are reactive. Pushing to an array or setting a nested property triggers updates.
<script> let todos = $state([{ text: 'learn runes', done: false }]);
function toggle(todo) { todo.done = !todo.done; // mutating a nested property is reactive } function add(text) { todos.push({ text, done: false }); // mutating the array is reactive }</script>This is a real difference from React, where you must produce new objects/arrays. In Svelte, todos.push(...) and todo.done = true just work — the proxy notices the mutation. The reactivity is deep: nested objects and arrays are proxied too.
flowchart LR write["mutate: todo.done = true"] --> proxy["$state proxy notices the change"] proxy --> update["update exactly the dependent DOM"]
Opting out: $state.raw
Section titled “Opting out: $state.raw”Deep proxying costs a little and isn’t always wanted — for large immutable data you only ever replace, use $state.raw. Raw state is reactive on reassignment but not on mutation.
<script> let settings = $state.raw({ theme: 'dark', density: 'cozy' });
// ✅ Reassigning a whole new object IS reactive: settings = { ...settings, theme: 'light' };
// ❌ Mutating is NOT tracked with raw state: // settings.theme = 'light'; // no update</script>Use $state.raw for values you treat immutably (replace wholesale) or large structures where you don’t want the proxy overhead.
Getting a plain copy: $state.snapshot
Section titled “Getting a plain copy: $state.snapshot”Because $state objects are proxies, passing one to code that expects a plain object (a console.log that shows a Proxy, structuredClone, a third-party library) can be surprising. $state.snapshot returns a plain, non-reactive copy.
<script> let user = $state({ name: 'Ada', roles: ['admin'] });
function save() { const plain = $state.snapshot(user); // a normal object, not a proxy api.save(plain); }</script>