Svelte 5 Runes, Side by Side with Svelte 4

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.

Svelte 4 syntax and the rune that replaces it
JobSvelte 4Svelte 5
Reactive variablelet count = 0let count = $state(0)
Computed value$: doubled = count * 2let doubled = $derived(count * 2)
Side effect$: console.log(count)$effect(() => console.log(count))
Propsexport let namelet { name } = $props()
Bindable propexport let valuelet { value = $bindable() } = $props()
Event handleron:click={fn}onclick={fn}
Component eventscreateEventDispatcherCallback 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.

Svelte 4 code and its Svelte 5 rewrite

<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.

Sharing state between components

A reactive store in a .svelte.js file

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);
}

$state is deep by default

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.

$derived must stay pure

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.

$effect timing and cleanup

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.

How it works

  1. Select a rune or syntax change from the buttons.
  2. Compare the Svelte 4 code on the left with the Svelte 5 code on the right.
  3. Read the note for the rule that catches people out.
  4. Copy the Svelte 5 version into your component.

Tips

Frequently asked questions

Do I have to rewrite my whole app for Svelte 5?

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.

Why is my $effect not re-running?

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.

Can I change a $derived value?

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.

What replaced createEventDispatcher?

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.

How do I give a form field a unique id?

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.

More in Guides

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.

Product

Who It's For

Features

Resources

Company

View Sitemap