Svelte 5 Patterns
Quick Guide: Svelte 5 uses Runes for explicit reactivity. Use$statefor reactive variables,$derivedfor computed values,$effectonly as an escape hatch. Use snippets instead of slots. Use callback props instead of event dispatchers. Keep components small and composable.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)
(You MUST use Svelte 5 Runes syntax — NOT Svelte 4 patterns like export let, $:, or stores for component state)
(You MUST use $derived for computed values — NEVER use $effect to synchronize state)
(You MUST use snippets ({#snippet} / {@render}) instead of slots (<slot>))
(You MUST use callback props (onclick, onsomething) instead of createEventDispatcher)
(You MUST use $state.raw() for large objects/arrays that are replaced, not mutated)
(You MUST use createContext for type-safe context instead of raw setContext/getContext with string keys)
</critical_requirements>
Auto-detection: Svelte 5, Runes, $state, $derived, $effect, $props, $bindable, $inspect, .svelte, snippet, @render, createContext, getContext, setContext, $state.raw, $state.eager, $derived.by, $effect.pre, ClassValue
When to use:
- Building Svelte 5 components with Runes reactivity
- Managing component state with
$stateand computed values with$derived - Creating reusable markup with snippets (replacing slots)
- Handling events with native event attributes and callback props
- Sharing state across components with context API
- Two-way binding with
$bindableprops
Key patterns covered:
- Runes:
$state,$derived,$effect,$props,$bindable,$inspect - Component composition with snippets and
{@render} - Event handling with native attributes and callback props
- Context API with
createContextfor type-safe cross-component state - Class-based reactive state with
$statefields - Deep vs shallow reactivity (
$statevs$state.raw)
When NOT to use:
- Meta-framework-specific patterns (routing, load functions, form actions) — use the corresponding meta-framework skill
- Svelte 4 patterns (
export let,$:reactive statements,<slot>,createEventDispatcher) - Server-side logic (use your meta-framework's server hooks and routes)
Detailed Resources:
- For decision frameworks and anti-patterns, see [reference.md](reference.md)
Runes & Reactivity:
- [examples/core.md](examples/core.md) -
$state,$derived,$effect,$props,$bindable, component patterns
Component Patterns:
- [examples/snippets.md](examples/snippets.md) - Snippet blocks,
{@render}, passing snippets as props, replacing slots - [examples/events.md](examples/events.md) - Event handling, component events via callback props, event modifiers
Advanced:
- [examples/advanced.md](examples/advanced.md) -
$inspect, context API,$state.raw,$state.eager, class-based state, shared state modules
<philosophy>
Philosophy
Svelte 5 introduces Runes — a set of primitives that bring explicit, fine-grained reactivity to Svelte. Unlike Svelte 4's compiler magic ($:, export let), Runes make reactivity visible and portable across .svelte files, .ts files, and class definitions.
Core principles:
- Explicit reactivity — Runes (
$state,$derived,$effect) make reactive declarations visible. No hidden compiler transformations. - Derived over effects — Compute values with
$derived, not$effect. Effects are escape hatches, not primary tools. - Deep reactivity by default —
$statecreates deeply reactive proxies for objects/arrays. Mutations are tracked automatically. - Snippets replace slots —
{#snippet}blocks are more powerful, typed, and composable than<slot>elements. - Callback props replace event dispatchers — Pass
onsomethingcallback props instead of usingcreateEventDispatcher. - Compile-time optimization — Svelte compiles components to efficient imperative code. No virtual DOM diffing at runtime.
When to use Svelte 5 Runes:
- All new Svelte components (Runes are the default in Svelte 5)
- Reactive state in
.svelte.tsor.svelte.jsfiles - Class-based state with reactive fields
- Any computed value that depends on reactive state
When NOT to use:
- Non-reactive constants (use plain
constorlet) - Server-side code that doesn't need reactivity
- Meta-framework concerns (routing, load functions, server hooks) — use the corresponding meta-framework skill
- Svelte 4 patterns —
export let,$:, stores for component state,<slot>,createEventDispatcher
</philosophy>
<patterns>
Core Patterns
Pattern 1: Reactive State with $state
Use $state to declare reactive variables. Updates to $state variables automatically trigger UI re-renders.
<!-- counter.svelte -->
<script lang="ts">
let count = $state(0);
const STEP = 5;
function increment() {
count += 1;
}
function incrementByStep() {
count += STEP;
}
</script>
<button onclick={increment}>
Count: {count}
</button>
<button onclick={incrementByStep}>
+{STEP}
</button>
Why good: Explicit reactive declaration, named constants for magic numbers, plain function event handlers
<!-- BAD: Svelte 4 style -->
<script>
let count = 0; // Not explicitly reactive in Svelte 5 mode
$: doubled = count * 2; // Svelte 4 reactive statement
</script>
Why bad: $: is Svelte 4 syntax deprecated in Svelte 5, implicit reactivity is confusing and non-portable
Deep Reactivity
$state creates deep proxies for objects and arrays — mutations are tracked automatically:
<script lang="ts">
interface Todo {
done: boolean;
text: string;
}
let todos = $state<Todo[]>([
{ done: false, text: 'Learn Svelte 5' }
]);
function addTodo(text: string) {
todos.push({ done: false, text }); // Mutation tracked!
}
function toggleTodo(index: number) {
todos[index].done = !todos[index].done; // Deep mutation tracked!
}
</script>
Why good: No need for immutable update patterns, array methods like .push() trigger reactivity, property mutations tracked deeply
Pattern 2: Computed Values with $derived
Use $derived for values that depend on other reactive state. Never use $effect to synchronize state.
<script lang="ts">
let count = $state(0);
// Simple expression
let doubled = $derived(count * 2);
// Complex computation with $derived.by
let stats = $derived.by(() => {
const isEven = count % 2 === 0;
const isPositive = count > 0;
return { isEven, isPositive };
});
</script>
<p>{count} doubled is {doubled}</p>
<p>Even: {stats.isEven}, Positive: {stats.isPositive}</p>
Why good: Automatically recalculates when dependencies change, no side effects, push-pull reactivity avoids unnecessary recalculations
<!-- BAD: Using $effect to synchronize state -->
<script lang="ts">
let count = $state(0);
let doubled = $state(0);
$effect(() => {
doubled = count * 2; // WRONG: Use $derived instead
});
</script>
Why bad: $effect for derived state creates unnecessary reactive subscriptions, runs after DOM update (timing issues), harder to reason about data flow
Pattern 3: Component Props with $props
Use $props to declare component inputs. Supports destructuring, defaults, rest props, and TypeScript.
<!-- user-card.svelte -->
<script lang="ts">
interface Props {
name: string;
email: string;
role?: string;
class?: string;
}
let { name, email, role = 'member', ...rest }: Props = $props();
// Derived from props — updates when props change
let initials = $derived(
name.split(' ').map(n => n[0]).join('').toUpperCase()
);
</script>