Svelte 5
Svelte 5 replaces stores with runes (reactive primitives) and class-based state (the idiomatic replacement for writable/readable). Don't mix Svelte 4 and 5 syntax — pick one.
Topics
| Topic | File |
|---|---|
| Rune patterns, Svelte 4→5 migration, component API | runes.md |
| Class patterns, context API, SSR safety | class-state.md |
Runes Quick Reference
Which rune? State: $state() | Computed: $derived() | Side effect: $effect() | Props: $props() | Bindable: $bindable()
Runes are top-level only — you can't call them inside functions or blocks.
<script>
let count = $state(0);
const doubled = $derived(count * 2);
$effect(() => {
console.log(`Count is ${count}`);
});
</script>
<button onclick={() => count++}>
{count} (doubled: {doubled})
</button>
Key behaviors:
- Objects and arrays are deeply reactive by default
$derivedcan be reassigned (Svelte 5.25+) — useconstfor read-only derived values- Event handlers:
onclicknoton:click - Children:
{@render children()}not<slot />
→ Deep dives: runes.md for full rune patterns, migration guide, and component API.
Class-Based State
Classes with $state fields are the idiomatic way to manage client-side state. More performant than stores, better TypeScript support, cleaner encapsulation.
File extension: .svelte.ts required for $state outside components.
// CounterState.svelte.ts
interface CounterState {
count: number;
increment: () => void;
}
class CounterStateClass implements CounterState {
count = $state(0);
increment = () => { this.count++; }; // Arrow methods avoid `this` binding issues
}
export const createCounterState = () => new CounterStateClass();
<script>
import { createCounterState } from './CounterState.svelte';
const state = createCounterState();
</script>
<button onclick={state.increment}>{state.count}</button>
Sharing State (Context API)
Local only → instantiate directly. Shared across components → use context:
// CounterState.svelte.ts
import { getContext, setContext, hasContext } from 'svelte';
const KEY = Symbol('counter'); // Symbol keys avoid collisions
export const setCounterState = () => setContext(KEY, new CounterStateClass());
export const getCounterState = () => {
if (!hasContext(KEY)) throw new Error('Counter context not set');
return getContext<CounterState>(KEY);
};
<!-- Parent: set context -->
<script>
import { setCounterState } from './CounterState.svelte';
setCounterState();
</script>
<!-- Any descendant: consume -->
<script>
import { getCounterState } from './CounterState.svelte';
const state = getCounterState();
</script>
⚠️ SvelteKit users: Never export module-level state instances — causes SSR leaks where state bleeds between requests.
→ Deep dives: class-state.md for arrays, timers, async patterns, and context vs scoped decisions.
Common Mistakes
| Mistake | Fix |
|---|---|
on:click={handler} | onclick={handler} (Svelte 5 syntax) |
<slot /> | {@render children()} |
$state inside a function | Move to top level of <script> or class field |
Exporting module-level $state instance | Use factory function or context API |
let x = $derived(...) then reassigning | Use const if you want read-only |
.ts extension for reactive files | Must be .svelte.ts for $state outside components |
Mixing writable()/readable() with runes | Pick one — don't mix Svelte 4/5 patterns |
Reference Index
Runes: Reactivity Patterns · Migration Gotchas · Component API · Snippets vs Slots · Common Mistakes
Class State: Class Patterns · Context vs Scoped · Common Mistakes · SSR Safety
