Runes are the compiler keywords Svelte 5 uses for reactivity. $state declares a reactive variable, $derived computes a value from other state, $effect runs code after the page updates, $props reads component props and $bindable marks a prop a parent may bind to. They replace Svelte 4's reactive let, the $: label and export let.
| Job | Svelte 4 | Svelte 5 |
|---|---|---|
| Reactive variable | let count = 0 | let count = $state(0) |
| Computed value | $: doubled = count * 2 | let doubled = $derived(count * 2) |
| Side effect | $: console.log(count) | $effect(() => console.log(count)) |
| Props | export let name | let { name } = $props() |
| Bindable prop | export let value | let { value = $bindable() } = $props() |
| Event handler | on:click={fn} | onclick={fn} |
| Component events | createEventDispatcher | Callback props |
| Slots | <slot /> | {#snippet} and {@render} |
Updated
Choose a rune to see a Svelte 4 component and its Svelte 5 rewrite. Each pair behaves the same, so you can migrate one file at a time and check the result as you go.
<script>
let count = 0;
</script>
<button on:click={() => count += 1}>{count}</button><script>
let count = $state(0);
</script>
<button onclick={() => count += 1}>{count}</button>A plain let is no longer reactive in runes mode. Wrap the starting value in $state and update it like any variable.
A reassigned $state variable cannot be exported directly, so export an object and change its properties instead.
// cart.svelte.js
export const cart = $state({ items: [] });
export function addItem(name) {
cart.items.push(name);
}
Wrapping an array or plain object in $state gives you a deeply reactive proxy. Pushing to the array or setting a nested property updates only the parts of the page that read it, with no need to reassign the whole value.
Class fields can be state too: declare a field with $state inside a class and the compiler turns it into a getter and setter pair.
The expression inside $derived should only calculate. Svelte refuses state changes such as count++ inside it. For longer logic, $derived.by takes a function whose return value becomes the derived value.
Deriveds are lazy: Svelte marks them stale when a dependency changes and recalculates on the next read, skipping downstream work when the result is the same.
An effect runs after the component mounts and again after its dependencies change, once the DOM has been updated. Return a function from the effect to clean up timers or listeners before the next run and when the component is destroyed.
If you need to measure or adjust something before the DOM changes, such as keeping a chat log scrolled to the bottom, use $effect.pre instead.
No. Svelte 5 still supports the Svelte 4 syntax, and old-style and rune-based components can sit in the same app and use each other, so you can convert files one by one.
Svelte only tracks state read synchronously inside the effect. A value read after an await or inside a setTimeout callback is not a dependency, so changing it does nothing.
Since Svelte 5.25 you can temporarily reassign a derived value, which helps with optimistic updates, unless it is declared with const. It recalculates when its dependencies change. Earlier versions treat deriveds as read-only.
Callback props. The child receives a function such as onsave through $props and calls it, and the parent passes the handler like any other attribute.
Call $props.id(), added in Svelte 5.20. It returns an id unique to the component instance that matches between server and browser, so it is safe for for and aria-labelledby.
Related: Svelte vs React, React hooks cheat sheet, Astro vs Next.js, JavaScript event listeners.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.