Svelte 5 replaced its compiler magic with runes — explicit reactive primitives that work everywhere, not just in .svelte files.
What Are Runes?
Runes are Svelte 5's new reactivity system. Instead of the compiler magically making let reactive, you explicitly mark state with $state, effects with $effect, and derived values with $derived.
$state — Reactive Variables
<script>
let count = $state(0);
let user = $state({ name: 'Alice', age: 30 });
let items = $state(['apple', 'banana']);
</script>
<button onclick={() => count++}>
Count: {count}
</button>
<button onclick={() => user.name = 'Bob'}>
Change name (deep reactivity!)
</button>
<button onclick={() => items.push('cherry')}>
Add item (array mutations work!)
</button>
$derived — Computed Values
<script>
let count = $state(0);
let doubled = $derived(count * 2);
let isEven = $derived(count % 2 === 0);
// Complex derivations
let items = $state([1, 2, 3, 4, 5]);
let total = $derived(items.reduce((sum, n) => sum + n, 0));
let filtered = $derived(items.filter(n => n > 2));
</script>
<p>{count} x 2 = {doubled}</p>
<p>{count} is {isEven ? 'even' : 'odd'}</p>
$effect — Side Effects
<script>
let count = $state(0);
$effect(() => {
console.log('Count changed:', count);
// Automatically re-runs when count changes
});
$effect(() => {
document.title = `Count: ${count}`;
});
// Cleanup
$effect(() => {
const interval = setInterval(() => count++, 1000);
return () => clearInterval(interval); // cleanup function
});
</script>
$props — Component Props
<!-- Button.svelte -->
<script>
let { label, onclick, variant = 'primary', ...rest } = $props();
</script>
<button class={variant} {onclick} {...rest}>
{label}
</button>
<!-- Usage -->
<Button label="Click me" onclick={() => alert('hi')} variant="secondary" />
$bindable — Two-Way Binding Props
<!-- TextInput.svelte -->
<script>
let { value = $bindable('') } = $props();
</script>
<input bind:value />
<!-- Usage -->
<script>
let name = $state('');
</script>
<TextInput bind:value={name} />
<p>Hello, {name}</p>
Runes in .ts Files (Not Just Svelte!)
// counter.svelte.ts
export function createCounter(initial = 0) {
let count = $state(initial);
let doubled = $derived(count * 2);
function increment() { count++; }
function decrement() { count--; }
function reset() { count = initial; }
return {
get count() { return count; },
get doubled() { return doubled; },
increment,
decrement,
reset,
};
}
<script>
import { createCounter } from './counter.svelte.ts';
const counter = createCounter(10);
</script>
<p>{counter.count} (doubled: {counter.doubled})</p>
<button onclick={counter.increment}>+</button>
Svelte 4 vs Svelte 5
| Feature | Svelte 4 | Svelte 5 |
|---|---|---|
| State | let x = 0 |
let x = $state(0) |
| Derived | $: doubled = x * 2 |
let doubled = $derived(x * 2) |
| Effects | $: console.log(x) |
$effect(() => console.log(x)) |
| Props | export let x |
let { x } = $props() |
| Deep Reactivity | No | Yes |
| Works in .ts | No | Yes (.svelte.ts) |
| Explicit | No (magic) | Yes (runes) |
Building data-driven Svelte apps? Check out my Apify actors — get clean data for your Svelte dashboards. For custom solutions, email spinov001@gmail.com.
Top comments (0)