In Svelte 5, you can create reactive state anywhere in your app — not just at the top level of your components.
Suppose we have a component like this:
<script>
let count = $state(0);
function increment() {
count += 1;
}
</script>
<button on:click={increment}>
clicks: {count}
</button>
We can encapsulate this logic in a function, so that it can be used in multiple places:
<script>
function createCounter() {
let count = $state(0);
function increment() {
count += 1;
}
return {
get count() { return count },
increment
};
}
const counter = createCounter();
</script>
<button on:click={increment}>
clicks: {count}
<button on:click={counter.increment}>
clicks: {counter.count}
</button>
Note that we're using a
get
property in the returned object, so thatcounter.count
always refers to the current value rather than the value at the time thecreateCounter
function was called.
We can also extract that function out into a separate .svelte.js
or .svelte.ts
module...
ts
export functioncreateCounter () {letcount =$state (0);functionincrement () {count += 1;}return {getcount () {returncount ;},increment };}
...and import it into our component:
<script>
import { createCounter } from './counter.svelte.js';
function createCounter() {...}
const counter = createCounter();
</script>
<button on:click={counter.increment}>
clicks: {counter.count}
</button>
See this example in the playground.
Stores equivalentpermalink
In Svelte 4, the way you'd do this is by creating a custom store, perhaps like this:
ts
import {writable } from 'svelte/store';export functioncreateCounter () {const {subscribe ,update } =writable (0);functionincrement () {update ((count ) =>count + 1);}return {subscribe ,increment };}
Back in the component, we retrieve the store value by prefixing its name with $
:
<script>
import { createCounter } from './counter.js';
const counter = createCounter();
</script>
<button on:click={counter.increment}>
clicks: {counter.count}
clicks: {$counter}
</button>
The store approach has some significant drawbacks. A counter is just about the simplest custom store we could create, and yet we have to completely change how the code is written — importing writable
, understanding its API, grabbing references to subscribe
and update
, changing the implementation of increment
from count += 1
to something far more cryptic, and prefixing the store name with a $
to retrieve its value. That's a lot of stuff you need to understand.
With runes, we just copy the existing code into a new function.
Gotchaspermalink
Reactivity doesn't magically cross function boundaries. In other words, replacing the get
property with a regular property wouldn't work...
export function createCounter() {
let count = $state(0);
function increment() {
count += 1;
}
return {
get count() { return count },
count,
increment
};
}
...because the value of count
in the returned object would always be 0
. Using the $state
rune doesn't change that fact — it simply means that when you do read count
(whether via a get
property or a normal function) inside your template or inside an effect, Svelte knows what to update when count
changes.