The Runes Revolution
Svelte 5 represents a fundamental shift in how we write reactive components. The new runes API makes reactivity explicit and more powerful.
State Management with $state
In Svelte 4, reactivity was implicit:
// Svelte 4
let count = 0;
function increment() {
count++; // This triggers reactivity
} Svelte 5 makes it explicit:
// Svelte 5
let count = $state(0);
function increment() {
count++; // Still reactive, but the source is clear
} Derived Values with $derived
The $derived rune replaces reactive statements and is more composable:
let firstName = $state('Ethan');
let lastName = $state('Hurst');
// Derived value automatically updates
let fullName = $derived(`${firstName} ${lastName}`);
// You can also use functions for complex derivations
let greeting = $derived(() => {
const time = new Date().getHours();
if (time < 12) return `Good morning, ${fullName}`;
if (time < 18) return `Good afternoon, ${fullName}`;
return `Good evening, ${fullName}`;
}); Effects with $effect
Side effects are now first-class citizens:
let theme = $state('light');
$effect(() => {
// This runs whenever theme changes
document.documentElement.classList.toggle('dark', theme === 'dark');
}); Props: The New Way
Props in Svelte 5 use destructuring syntax:
<script>
// Props are defined at the top level
let { title, description, tags = [] } = $props();
</script>
<article>
<h1>{title}</h1>
<p>{description}</p>
<ul>
{#each tags as tag}
<li>{tag}</li>
{/each}
</ul>
</article> Real-World Example: Counter Component
Here’s a complete component showing runes in action:
<script lang="ts">
// State
let count = $state(0);
let step = $state(1);
// Derived values
let isEven = $derived(count % 2 === 0);
let doubled = $derived(count * 2);
// Effects
$effect(() => {
console.log(`Count changed to ${count}`);
});
// Functions
function increment() {
count += step;
}
function decrement() {
count -= step;
}
function reset() {
count = 0;
}
</script>
<div class="counter">
<h2>Count: {count}</h2>
<p>Doubled: {doubled}</p>
<p>Is Even: {isEven}</p>
<input type="number" bind:value={step} min="1" />
<button onclick={decrement}>-</button>
<button onclick={reset}>Reset</button>
<button onclick={increment}>+</button>
</div> Type Safety with TypeScript
Svelte 5 has excellent TypeScript support:
interface CounterProps {
initialCount?: number;
onCountChange?: (count: number) => void;
}
let {
initialCount = 0,
onCountChange
}: CounterProps = $props();
let count = $state(initialCount);
$effect(() => {
onCountChange?.(count);
}); Performance Considerations
Svelte 5’s runes enable better performance optimizations:
- Fine-grained reactivity - Only the exact values that change trigger updates
- No virtual DOM - Direct DOM manipulation means less overhead
- Compile-time optimization - The compiler can optimize based on rune usage
Migration Strategy
Moving from Svelte 4 to 5:
# Install Svelte 5
npm install svelte@next
# Update your components gradually
# Both syntaxes work during migration Key migration points:
- Replace reactive assignments with
$state - Convert reactive statements to
$derived - Update stores to use runes where appropriate
- Change component props to use destructuring
Conclusion
Svelte 5’s runes API is a massive improvement. The explicit reactivity model makes code easier to understand and maintain, while the compiler ensures optimal performance.
The learning curve is gentle - if you know Svelte 4, you’ll feel at home in Svelte 5. The new primitives just make everything clearer.
I’m excited to build more with Svelte 5 and share what I learn along the way.