Reactivity
Svelte 5 runes system for fine-grained reactivity with $state, $derived, and $effect
You are an expert in Svelte 5's runes-based reactivity system, helping developers build performant reactive UIs using $state, $derived, $effect, and related primitives.
## Key Points
- **Infinite `$effect` Loops** — reading and writing the same `$state` variable inside a single `$effect` without using `untrack`, causing the effect to trigger itself recursively.
- **Use `$state.raw` for large collections** that are replaced wholesale rather than mutated, avoiding the overhead of deep proxying.
- **Return cleanup functions from `$effect`** whenever you create subscriptions, timers, or abort controllers.
- **Keep effects focused.** A single `$effect` should do one thing. Split unrelated side effects into separate `$effect` calls.
- **Use `.svelte.js`/`.svelte.ts` extensions** for any module file that uses runes outside a component.
- **Destructuring loses reactivity.** `let { name } = $state({ name: 'A' })` makes `name` a plain string. Keep the object reference intact or use `$derived` on the property.
- **`$effect` runs after mount, not during SSR.** Do not rely on `$effect` for server-side logic; it only executes in the browser.
- **Infinite loops with `$effect`.** Writing to a reactive variable that the same `$effect` reads causes an infinite re-run. Use `untrack` or restructure the logic.
- **Forgetting `.svelte.js` extension.** Runes in a plain `.js` file will not be compiled — the file must use `.svelte.js` or `.svelte.ts`.
- **Comparing `$state` objects by reference.** Deep-reactive state is wrapped in a proxy, so `===` comparison against the original object will fail. Compare by value or use `$state.snapshot()`.skilldb get svelte-skills/ReactivityFull skill: 238 linesReactivity — Svelte 5 Runes
You are an expert in Svelte 5's runes-based reactivity system, helping developers build performant reactive UIs using $state, $derived, $effect, and related primitives.
Core Philosophy
Svelte 5's runes represent a fundamental shift in how Svelte handles reactivity: from implicit compiler magic to explicit, composable primitives. In earlier Svelte versions, let x = 0 was magically reactive inside components, but this magic did not extend to external modules, classes, or complex state shapes. Runes fix this by making reactivity a deliberate choice — $state, $derived, $effect — that works uniformly in components, .svelte.js modules, and class bodies.
The hierarchy of runes reflects a clear design philosophy: state is the foundation, derivations build on state, and effects are the escape hatch for the outside world. $state declares what can change. $derived declares what is computed from state without side effects. $effect is reserved for things that interact with the world outside the reactive graph — DOM manipulation, network calls, logging. This hierarchy is not arbitrary; it maps directly to the functional reactive programming principle that pure derivations should vastly outnumber side effects.
Deep reactivity in $state is the default because it matches how developers think about data. Pushing an item to an array should trigger an update. Changing a nested property should trigger an update. When deep reactivity is too expensive, $state.raw offers an opt-out for large datasets that are replaced wholesale. This opt-in simplicity, opt-out performance model means the common case is easy and the advanced case is possible.
Anti-Patterns
-
Using
$effectfor Derived Values — writing to a$statevariable inside an$effectto compute a value that could be expressed as$derived. This creates unnecessary re-render cycles and makes the dependency graph harder to trace. -
Destructuring
$stateObjects — writinglet { name } = $state({ name: 'A' })expectingnameto be reactive, when destructuring extracts a plain value from the proxy. Keep the object reference intact or use$derivedon individual properties. -
Infinite
$effectLoops — reading and writing the same$statevariable inside a single$effectwithout usinguntrack, causing the effect to trigger itself recursively. -
Runes in Plain
.jsFiles — using$state,$derived, or$effectin a file with a.jsor.tsextension instead of.svelte.jsor.svelte.ts. The Svelte compiler does not process plain JavaScript files, so runes are treated as undefined variables. -
Reference Comparisons on
$stateProxies — comparing$stateobjects with===against the original object, which fails because$statewraps values in a deep reactive proxy. Use$state.snapshot()or compare by value instead.
Overview
Svelte 5 replaces the implicit reactivity model of earlier versions with explicit runes — compiler-understood signals that give developers fine-grained control over reactive state, derived computations, and side effects. Runes work in .svelte files and in .svelte.js/.svelte.ts modules.
Core Concepts
$state — Reactive State
Declares a piece of reactive state. The compiler tracks reads and writes to trigger updates.
<script>
let count = $state(0);
let user = $state({ name: 'Alice', age: 30 });
</script>
<button onclick={() => count++}>
Clicked {count} times
</button>
Deep reactivity: objects and arrays declared with $state are deeply reactive — mutating nested properties triggers updates automatically.
<script>
let todos = $state([
{ text: 'Learn runes', done: false }
]);
function toggle(index) {
todos[index].done = !todos[index].done; // triggers update
}
</script>
$state.raw — Shallow State
When deep reactivity is unnecessary or expensive, use $state.raw for reference-equality-only tracking.
<script>
let items = $state.raw([1, 2, 3]);
function replace() {
// Must reassign — mutations are NOT tracked
items = [...items, 4];
}
</script>
$derived — Computed Values
Derives a value from other reactive state. Re-evaluates only when its dependencies change.
<script>
let width = $state(10);
let height = $state(20);
let area = $derived(width * height);
</script>
<p>Area: {area}</p>
$derived.by — Complex Derivations
For derivations that need a function body with intermediate variables or logic.
<script>
let items = $state([5, 3, 8, 1]);
let stats = $derived.by(() => {
const sorted = [...items].sort((a, b) => a - b);
return {
min: sorted[0],
max: sorted[sorted.length - 1],
sum: sorted.reduce((a, b) => a + b, 0)
};
});
</script>
$effect — Side Effects
Runs a function when its reactive dependencies change. Automatically cleans up on re-run and on destroy.
<script>
let query = $state('');
$effect(() => {
const controller = new AbortController();
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(r => r.json())
.then(data => { /* update state */ });
return () => controller.abort(); // cleanup
});
</script>
$effect.pre — Pre-DOM-Update Effects
Runs before the DOM updates, useful for measuring or preparing layout work.
<script>
let messages = $state([]);
let container;
$effect.pre(() => {
// Check scroll position before DOM updates with new messages
messages.length; // track dependency
// ... measurement logic
});
</script>
Implementation Patterns
Reactive Classes
Runes work inside class bodies, making it easy to build reusable reactive models.
// counter.svelte.js
export class Counter {
count = $state(0);
doubled = $derived(this.count * 2);
increment() {
this.count++;
}
reset() {
this.count = 0;
}
}
<script>
import { Counter } from './counter.svelte.js';
const counter = new Counter();
</script>
<button onclick={() => counter.increment()}>
{counter.count} (doubled: {counter.doubled})
</button>
Shared Reactive State Across Components
Extract state into .svelte.js modules for cross-component sharing.
// cart.svelte.js
let items = $state([]);
export function addItem(item) {
items.push(item);
}
export function getItems() {
return items;
}
export const totalPrice = $derived(
items.reduce((sum, i) => sum + i.price, 0)
);
Untracking Dependencies
Use untrack to read reactive state without creating a dependency.
<script>
import { untrack } from 'svelte';
let count = $state(0);
let log = $state([]);
$effect(() => {
// Only re-run when count changes, not when log changes
const current = count;
const prevLog = untrack(() => log);
log = [...prevLog, `count is ${current}`];
});
</script>
Best Practices
- Prefer
$derivedover$effectfor computed values. If the goal is to compute a new value from state, use$derived. Reserve$effectfor true side effects (fetching, logging, DOM manipulation). - Use
$state.rawfor large collections that are replaced wholesale rather than mutated, avoiding the overhead of deep proxying. - Return cleanup functions from
$effectwhenever you create subscriptions, timers, or abort controllers. - Keep effects focused. A single
$effectshould do one thing. Split unrelated side effects into separate$effectcalls. - Use
.svelte.js/.svelte.tsextensions for any module file that uses runes outside a component.
Common Pitfalls
- Destructuring loses reactivity.
let { name } = $state({ name: 'A' })makesnamea plain string. Keep the object reference intact or use$derivedon the property. $effectruns after mount, not during SSR. Do not rely on$effectfor server-side logic; it only executes in the browser.- Infinite loops with
$effect. Writing to a reactive variable that the same$effectreads causes an infinite re-run. Useuntrackor restructure the logic. - Forgetting
.svelte.jsextension. Runes in a plain.jsfile will not be compiled — the file must use.svelte.jsor.svelte.ts. - Comparing
$stateobjects by reference. Deep-reactive state is wrapped in a proxy, so===comparison against the original object will fail. Compare by value or use$state.snapshot().
Install this skill directly: skilldb add svelte-skills
Related Skills
Component Patterns
Svelte component composition patterns including props, snippets, context, and advanced reuse techniques
Form Actions
SvelteKit form actions for progressive enhancement with server-side form handling
Load Functions
SvelteKit server and universal load functions for fetching and passing data to pages and layouts
Stores
Svelte stores and state management patterns including writable, readable, derived, and custom stores
Sveltekit Auth
Authentication patterns in SvelteKit using hooks, cookies, sessions, and OAuth flows
Sveltekit Routing
SvelteKit file-based routing system including layouts, groups, and advanced route patterns