React
Svelte 5 runes: state, derived values and effects
Runes replace the magic assignments of Svelte 4: state for state, derived for computed values, effect only for the DOM and external APIs.
Key points
- Runes are calls the compiler recognises: they work only in .svelte and .svelte.ts files.
- A computed value belongs in $derived, not in $effect: the effect is for what lives outside the component.
- $state wraps objects in a proxy; $state.raw suits large data you replace wholesale.
In Svelte 5 you declare state with $state: a rune is a call the compiler recognises, not a let whose role it has to guess. From that call it knows what to track and what to leave alone.
Four runes cover almost everything
$state for mutable state, $derived for computed values, $effect for side effects, $props for component input. $bindable, $inspect and $host cover narrower cases.
npx sv migrate svelte-5
let { start = 0 }: { start?: number } = $props();
let count = $state(start);
const double = $derived(count * 2);
(count += 1)}>Add one
{count} × 2 = {double}
$state wraps objects and arrays in a proxy, so nested changes are tracked. For large data you replace wholesale there is $state.raw; to hand an object to an external library use $state.snapshot, which returns a plain copy.
Derived is not an effect
$derived declares a value, $derived.by accepts a block of code. $effect runs after the DOM update, in the browser only, and is there for the DOM or external APIs: using it to align two states creates a loop the compiler does not report.
let query = $state("");
let items = $state([]);
const matches = $derived.by(() =>
items.filter((item) => item.name.toLowerCase().includes(query.toLowerCase())),
);
$effect(() => {
document.title = matches.length + " results";
});
Props with $props and $bindable
let { title, value = 0 } = $props() declares inputs and their defaults. $bindable() makes a prop writable from the outside: without it the assignment stays local to the child and the parent never sees the change.
| Rune | Purpose | Typical mistake |
|---|---|---|
| $state | Local mutable state | Declaring it in a .ts file without the .svelte suffix |
| $derived | Computed value | Assigning to it: it is read-only |
| $effect | Effects after the DOM | Syncing two states, which needs a derived value |
| $props | Component input | Reading a prop with no default and finding nothing |
The limitation to plan for
Runes exist only where the compiler sees them: in .svelte files and in files with the .svelte.ts suffix. A plain .ts module cannot hold $state, and $effect does not run during server rendering.
Mistakes that show up in the migration
- Mixing the old syntax and runes in one component: a component with runes does not accept
export let. - Assigning inside a
$derived: the value is read-only and the error only appears at runtime. - Using
$effectto sync two states: you want a$derived, the effect adds a loop. - Reading state after an
awaitinside an effect and expecting it to be tracked: read the dependency before the pause.
The Svelte components in the catalogue already use this form: flowbite-svelte and svelte-bits install as source, and the form block shows the case where local state is enough. Routing is in routing and layouts in SvelteKit, the old store model in a store that does not update, the comparison in Svelte vs React.
Frequently asked questions
Do I have to rewrite every component?
No. Runes mode is enabled per component and Svelte 5 still compiles the old syntax. Rewrite where state becomes hard to follow, not all at once.
$state or $state.raw?
$state for structures you change deeply, raw for large arrays and objects you replace wholesale. Raw does not track inner changes, so reassign the value.
Why does $effect run more than once?
Because the first pass registers dependencies and later passes apply the result. Too many passes mean the effect reads and writes the same state: move the calculation into a derived value.
Do runes work in a .ts file?
Only with the .svelte.ts suffix, which goes through the compiler. A plain .ts file is not processed and the rune stays a call to a function that does not exist.
Is Svelte 5 compatible with Svelte 4 libraries?
Mostly yes: old components still compile in legacy mode. The risk is actions that depend on this and implicit bindings, which runes no longer have.