Skip to content

Runes vs Stores

Before Svelte 5, sharing reactive state across components meant a store. Now you can put $state in a module and export it. Runes work outside components — but only in files with a .svelte.js or .svelte.ts extension, which tells the compiler to enable rune processing.

The store version:

counter.js
import { writable } from 'svelte/store';
export const count = writable(0);
<script>
import { count } from './counter.js';
</script>
<button onclick={() => count.update((n) => n + 1)}>{$count}</button>

The runes version:

// counter.svelte.js ← note the .svelte.js extension
export const counter = $state({ value: 0 });
<script>
import { counter } from './counter.svelte.js';
</script>
<button onclick={() => counter.value++}>{counter.value}</button>

No writable, no set/update, no $ prefix — you read and mutate counter.value like a normal object, and it’s reactive everywhere it’s used.

Notice the runes version exports { value: 0 }, not 0. A bare exported let x = $state(0) can’t stay linked across a module boundary — importing it copies the value, losing reactivity. Exporting an object (or a function returning reactive state) keeps everyone pointing at the same reactive proxy, so mutations to counter.value propagate. This is the one gotcha of module-level runes.

Store (writable)Runes (.svelte.js)
Read in component$countcounter.value
Updatecount.set(x) / count.update(fn)counter.value = x
Outside a componentworks anywhereneeds .svelte.js/.svelte.ts
The subscribe contractyes (interop, observables)no
Ceremonymore (methods, $)less (plain read/write)
flowchart TB
  q["Sharing reactive state?"] --> interop["Need the subscribe contract
(observables, external push source)?"]
  interop --> yes["Yes: use a store"]
  interop --> no["No: export $state from a .svelte.js module"]
Choosing runes or stores
  • Runes (default). Most shared app state — a cart, a theme, a current-user object. Simpler, less boilerplate, and the same mental model as component-local state.
  • Stores. When you need the contract: wrapping a WebSocket or other push source with readable, interoperating with RxJS-style observables, or maintaining existing store-based code. Stores aren’t legacy — they’re the specialized tool.
How do you use runes for state outside a component?
Why export an object like `$state({ value: 0 })` instead of a bare `$state(0)`?
In the runes version, how do you update shared state?
When are stores the better choice over runes?