Svelte 5 introduces Runes โ a universal, fine-grained reactivity system that replaces the Svelte 4 compiler-driven reactivity. This page documents production-ready patterns for Svelte 5 (v5.0+).
Core Runes
$state โ Reactive State
1
2
3
4
5
6
7
8
9
10
11
12
13
<script>// Primitive โ fine-grained
letcount=$state(0);// Object โ deeply reactive (proxied)
letuser=$state({name:'Ada',age:36});// Array โ deeply reactive
letitems=$state(['a','b','c']);// Raw โ no proxy, only reassignment triggers updates
letrawData=$state.raw(largeApiResponse);</script>
Key rules:
Only use $state for values that should trigger reactive updates
<script>letcount=$state(0);// Avoid: syncing state to external libs
$effect(()=>{d3.select('#chart').datum(data).call(chart);});// Prefer: {@attach} for DOM libs
// <div {@attach}={d3Chart} />
// Avoid: running code on user interaction
$effect(()=>{if(count>10)analytics.track('milestone');});// Prefer: event handler
functionincrement(){count++;if(count>10)analytics.track('milestone');}// Debugging: trace reactivity
$effect(()=>{$inspect.trace('count-effect');console.log(count);});</script>
Anti-patterns to avoid:
Updating state inside effects (causes infinite loops)
Using effects for event listeners (use <svelte:window onkeydown={...}>)
Wrapping effects in if (browser) โ effects don’t run on server
$props โ Component Props
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>// Destructure with defaults
let{title='Default',count=0,onClick}=$props();// Props are reactive โ use $derived for computed values
letstyle=$derived(count>10?'danger':'normal');// Bindable props
let{bind:value=''}=$props();</script>
Rules:
Treat props as mutable โ they can change from parent
Always use $derived for values computed from props
Use bind: on parent for two-way binding: <Child bind:value={val} />
$inspect โ Debugging
1
2
3
4
5
6
7
8
9
10
<script>// Trace what triggered an effect/derived
$effect(()=>{$inspect.trace('user-sync');syncToBackend(user);});// Log with label
$inspect('user-change',user);</script>
<script>// API response โ never mutated, only replaced
letposts=$state.raw([]);asyncfunctionload(){posts=awaitfetchPosts();// reassignment triggers update
}</script>
Avoid Unnecessary Reactivity
1
2
3
4
5
6
7
<script>// NOT reactive โ plain variable
constapiBase='https://api.example.com';// Only reactive if used in template/effect/derived
letcount=$state(0);</script>
Memoize Expensive Derivations
1
2
3
4
5
6
7
8
<script>letitems=$state([]);// $derived.by caches until dependencies change
letsorted=$derived.by(()=>[...items].sort((a,b)=>a.name.localeCompare(b.name)));</script>