Skip to main content

Examples

Universal reactivity

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 that counter.count always refers to the current value rather than the value at the time the createCounter function was called.

We can also extract that function out into a separate .svelte.js or .svelte.ts module...

ts
export function createCounter() {
let count = $state(0);
function increment() {
count += 1;
}
return {
get count() {
return count;
},
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 equivalent

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 function createCounter() {
const { subscribe, update } = writable(0);
function increment() {
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.

Gotchas

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.