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.

RunePurposeTypical mistake
$stateLocal mutable stateDeclaring it in a .ts file without the .svelte suffix
$derivedComputed valueAssigning to it: it is read-only
$effectEffects after the DOMSyncing two states, which needs a derived value
$propsComponent inputReading 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 $effect to sync two states: you want a $derived, the effect adds a loop.
  • Reading state after an await inside 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.