Ethan Hurst

Building with Svelte 5 - First Impressions

Ethan Hurst 3 min read

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:

  1. Replace reactive assignments with $state
  2. Convert reactive statements to $derived
  3. Update stores to use runes where appropriate
  4. 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.