Svelte 5 introduces runes, a powerful set of primitives for controlling reactivity inside your Svelte components and — for the first time — inside .svelte.js
and .svelte.ts
modules.
Runes are function-like symbols that provide instructions to the Svelte compiler. You don't need to import them from anywhere — when you use Svelte, they're part of the language.
When you opt in to runes mode, the non-runes features listed in the 'What this replaces' sections are no longer available.
Check out the Introducing runes blog post before diving into the docs!
$statepermalink
Reactive state is declared with the $state
rune:
<script>
let count = $state(0);
</script>
<button on:click={() => count++}>
clicks: {count}
</button>
You can also use $state
in class fields (whether public or private):
ts
classTodo {done =$state (false);text =$state ();constructor(text ) {this.text =text ;}}
In this example, the compiler transforms
done
andtext
intoget
/set
methods on the class prototype referencing private fields
What this replacespermalink
In non-runes mode, a let
declaration is treated as reactive state if it is updated at some point. Unlike $state(...)
, which works anywhere in your app, let
only behaves this way at the top level of a component.
$derivedpermalink
Derived state is declared with the $derived
rune:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
<button on:click={() => count++}>
{doubled}
</button>
<p>{count} doubled is {doubled}</p>
The expression inside $derived(...)
should be free of side-effects. Svelte will disallow state changes (e.g. count++
) inside derived expressions.
As with $state
, you can mark class fields as $derived
.
What this replacespermalink
The non-runes equivalent would be $: double = count * 2
. There are some important differences to be aware of:
- With the
$derived
rune, the value ofdouble
is always current (for example if you updatecount
then immediatelyconsole.log(double)
). With$:
declarations, values are not updated until right before Svelte updates the DOM - In non-runes mode, Svelte determines the dependencies of
double
by statically analysing thecount * 2
expression. If you refactor it...
...that dependency information is lost, andtsconstdoubleCount = () =>count * 2;$:double =doubleCount ();double
will no longer update whencount
changes. With runes, dependencies are instead tracked at runtime. - In non-runes mode, reactive statements are ordered topologically, meaning that in a case like this...
...ts$:triple =double +count ;$:double =count * 2;double
will be calculated first despite the source order. In runes mode,triple
cannot referencedouble
before it has been declared.
$effectpermalink
To run code whenever specific values change, or when a component is mounted to the DOM, we can use the $effect
rune:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
$effect(() => {
// runs when the component is mounted, and again
// whenever `count` or `doubled` change,
// after the DOM has been updated
console.log({ count, doubled });
return () => {
// if a callback is provided, it will run
// a) immediately before the effect re-runs
// b) when the component is destroyed
console.log('cleanup');
};
});
</script>
<button on:click={() => count++}>
{doubled}
</button>
<p>{count} doubled is {doubled}</p>
What this replacespermalink
The $effect
rune is roughly equivalent to $:
when it's being used for side-effects (as opposed to declarations). There are some important differences:
- Effects only run in the browser, not during server-side rendering
- They run after the DOM has been updated, whereas
$:
statements run immediately before - You can return a cleanup function that will be called whenever the effect refires
Additionally, you will most likely find you can use effects in all the places where you previously used onMount
and afterUpdate
(the latter of which will be deprecated in Svelte 5).
$effect.prepermalink
In rare cases, you may need to run code before the DOM updates. For this we can use the $effect.pre
rune:
<script>
import { tick } from 'svelte';
let div;
let messages = [];
// ...
$effect.pre(() => {
if (!div) return; // not yet mounted
// reference `messages` so that this code re-runs whenever it changes
messages;
// autoscroll when new messages are added
if (
div.offsetHeight + div.scrollTop >
div.scrollHeight - 20
) {
tick().then(() => {
div.scrollTo(0, div.scrollHeight);
});
}
});
</script>
<div bind:this={div}>
{#each messages as message}
<p>{message}</p>
{/each}
</div>
What this replacespermalink
Previously, you would have used beforeUpdate
, which — like afterUpdate
— is deprecated in Svelte 5.
$effect.activepermalink
The $effect.active
rune is an advanced feature that tells you whether or not the code is running inside an effect or inside your template (demo):
<script>
console.log('in component setup:', $effect.active()); // false
$effect(() => {
console.log('in effect:', $effect.active()); // true
});
</script>
<p>in template: {$effect.active()}</p> <!-- true -->
This allows you to (for example) add things like subscriptions without causing memory leaks, by putting them in child effects.
$effect.rootpermalink
The $effect.root
rune is an advanced feature that creates a non-tracked scope that doesn't auto-cleanup. This is useful for
nested effects that you want to manually control. This rune also allows for creation of effects outside of the component initialisation phase.
<script>
let count = $state(0);
const cleanup = $effect.root(() => {
$effect(() => {
console.log(count);
});
return () => {
console.log('effect root cleanup');
};
});
</script>
$propspermalink
To declare component props, use the $props
rune:
ts
let {optionalProp = 42,requiredProp } =$props ();
You can use familiar destructuring syntax to rename props, in cases where you need to (for example) use a reserved word like catch
in <MyComponent catch={22} />
:
ts
let {catch :theCatch } =$props ();
To get all properties, use rest syntax:
ts
let {a ,b ,c , ...everythingElse } =$props ();
If you're using TypeScript, you can use type arguments:
ts
let { a, b, c, ...everythingElse } = $props<MyProps>();
What this replacespermalink
$props
replaces the export let
and export { x as y }
syntax for declaring props. It also replaces $$props
and $$restProps
, and the little-known interface $$Props {...}
construct.
Note that you can still use export const
and export function
to expose things to users of your component (if they're using bind:this
, for example).
$inspectpermalink
The $inspect
rune is roughly equivalent to console.log
, with the exception that it will re-run whenever its
argument changes. $inspect
tracks reactive state deeply, meaning that updating something inside an object
or array using fine-grained reactivity will cause it to re-fire. (Demo:)
<script>
let count = $state(0);
let message = $state('hello');
$inspect({ count, message }); // will console.log when `count` or `message` change
</script>
<button onclick={() => count++}>Increment</button>
<input bind:value={message} />
If a callback is also provided, it will be invoked instead of console.log
. The first argument to the callback
is the current value. The second is either "init"
or "update"
. Demo:
<script>
let count = $state(0);
$inspect(count, (count, type) => {
if (type === 'update') {
debugger; // or `console.trace`, or whatever you want
}
});
</script>
<button onclick={() => count++}>Increment</button>
A convenient way to find the origin of some change is to pass console.trace
as the second argument:
ts
$inspect (stuff ,console .trace );
$inspect
only works during development.
How to opt inpermalink
Current Svelte code will continue to work without any adjustments. Components using the Svelte 4 syntax can use components using runes and vice versa.
The easiest way to opt in to runes mode is to just start using them in your code. Alternatively, you can force the compiler into runes or non-runes mode either on a per-component basis...
<!-- this can be `true` or `false` -->
<svelte:options runes={true} />
...or for your entire app:
ts
export default {compilerOptions : {runes : true}};