Svelte and SvelteKit course Β· Module 3: Reactivity and Runes
Comparison: Old vs New Syntax
In this lesson8
Let us see how the old Svelte 4 syntax translates to Runes in Svelte 5. You will meet the old syntax in existing projects and older tutorials, so it is worth recognizing it - but write new code with runes. In every pair, Svelte 4 (legacy) comes first and Svelte 5 below it.
State
<!-- Svelte 4 (legacy) -->
<script>
let count = 0;
let items = [];
</script><!-- Svelte 5 with Runes -->
<script>
let count = $state(0);
let items = $state([]);
</script>Reactive Declarations
<!-- Svelte 4 (legacy) -->
<script>
let count = 0;
$: doubled = count * 2;
$: tripled = count * 3;
$: total = doubled + tripled;
</script><!-- Svelte 5 with Runes -->
<script>
let count = $state(0);
let doubled = $derived(count * 2);
let tripled = $derived(count * 3);
let total = $derived(doubled + tripled);
</script>Reactive Statements
<!-- Svelte 4 (legacy) -->
<script>
let count = 0;
$: console.log('Count:', count);
$: {
if (count > 10) {
alert('A lot!');
}
}
</script><!-- Svelte 5 with Runes -->
<script>
let count = $state(0);
$effect(() => {
console.log('Count:', count);
});
$effect(() => {
if (count > 10) {
alert('A lot!');
}
});
</script>Props
<!-- Svelte 4 (legacy) -->
<script>
export let name;
export let count = 0;
</script><!-- Svelte 5 with Runes -->
<script>
let { name, count = 0 } = $props();
</script>Bindable Props
The Input.svelte component hands the typed value back to its parent through bind:value:
<!-- Svelte 4 (legacy) -->
<script>
export let value;
</script>
<input bind:value><!-- Svelte 5 with Runes -->
<script>
let { value = $bindable() } = $props();
</script>
<input bind:value>Usage in the parent looks the same in both versions:
<script>
import Input from './Input.svelte';
let myValue = $state('');
</script>
<Input bind:value={myValue} />
<p>Typed: {myValue}</p>Events
<!-- Svelte 4 (legacy) -->
<script>
let count = 0;
</script>
<button on:click={() => count++}>Clicked {count} times</button><!-- Svelte 5 with Runes -->
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>Clicked {count} times</button>When to Use Which?
| Situation | Svelte 4 (legacy) | Svelte 5 Runes |
|---|---|---|
| Simple state | let x = 0 | let x = $state(0) |
| Computation | $: y = x * 2 | let y = $derived(x * 2) |
| Effect | $: { ... } | $effect(() => { ... }) |
| Props | export let | $props() |
| DOM events | on:click={fn} | onclick={fn} |
| Component events | createEventDispatcher | functions in props, e.g. onselect |
| Content from the parent | <slot /> | {@render children()} |
Compatibility Mode - Component by Component
Svelte 5 supports both syntaxes, but not in one file. A component that uses even a single rune runs in runes mode - then $: and export let are compile errors in it. A component without runes runs in compatibility (legacy) mode. So you can migrate gradually, file by file.
The old component keeps working unchanged:
<!-- OldCounter.svelte - Svelte 4 (legacy) -->
<script>
let oldCount = 0;
$: oldDoubled = oldCount * 2;
</script>
<button on:click={() => oldCount++}>
Old counter: {oldCount} (x2 = {oldDoubled})
</button>A new component next to it uses runes:
<!-- NewCounter.svelte - Svelte 5 with Runes -->
<script>
let newCount = $state(0);
let newDoubled = $derived(newCount * 2);
</script>
<button onclick={() => newCount++}>
New counter: {newCount} (x2 = {newDoubled})
</button>And the parent uses both as if nothing happened:
<!-- App.svelte -->
<script>
import OldCounter from './OldCounter.svelte';
import NewCounter from './NewCounter.svelte';
</script>
<OldCounter />
<NewCounter />When you want to move a whole project to runes, the npx sv migrate svelte-5 command helps - it rewrites most of the code automatically.
Choose Runes for new projects - they are the present and the future of Svelte!
Docs entries used in this lesson
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What is the Svelte 5 equivalent of the old $: doubled = count * 2?
2. What is the Svelte 5 equivalent of the old 'export let name'?