DEV Community

Alex Spinov
Alex Spinov

Posted on

Svelte 5 Runes Have Changed Everything — Here's the Complete Guide to the New Reactivity System

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>
Enter fullscreen mode Exit fullscreen mode

$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>
Enter fullscreen mode Exit fullscreen mode

$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>
Enter fullscreen mode Exit fullscreen mode

$props — Component Props

<!-- Button.svelte -->
<script>
  let { label, onclick, variant = 'primary', ...rest } = $props();
</script>

<button class={variant} {onclick} {...rest}>
  {label}
</button>
Enter fullscreen mode Exit fullscreen mode
<!-- Usage -->
<Button label="Click me" onclick={() => alert('hi')} variant="secondary" />
Enter fullscreen mode Exit fullscreen mode

$bindable — Two-Way Binding Props

<!-- TextInput.svelte -->
<script>
  let { value = $bindable('') } = $props();
</script>

<input bind:value />
Enter fullscreen mode Exit fullscreen mode
<!-- Usage -->
<script>
  let name = $state('');
</script>

<TextInput bind:value={name} />
<p>Hello, {name}</p>
Enter fullscreen mode Exit fullscreen mode

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,
  };
}
Enter fullscreen mode Exit fullscreen mode
<script>
  import { createCounter } from './counter.svelte.ts';
  const counter = createCounter(10);
</script>

<p>{counter.count} (doubled: {counter.doubled})</p>
<button onclick={counter.increment}>+</button>
Enter fullscreen mode Exit fullscreen mode

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)