Deep patterns and best practices for Svelte 5's reactivity system. Use when working with $state, $derived, $effect, $props, $bindable, $inspect, or when users ask about Svelte 5 reactivity, state management, reactive declarations, or migrating from Svelte 4 reactive statements ($:).
Provides Svelte 5 runes guidance for $state, $derived, $effect, $props, and $bindable.
npx claudepluginhub maxnoller/claude-code-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
examples/counter.svelteexamples/effect-cleanup.svelteexamples/reactive-class.svelte.tsreferences/gotchas.mdreferences/migration.mdSvelte 5 introduces runes—compiler-driven reactivity primitives that replace Svelte 4's implicit reactivity. Runes provide fine-grained, predictable reactivity that works in .svelte and .svelte.ts files.
Declare reactive state that triggers updates when changed:
<script>
let count = $state(0);
let user = $state({ name: 'Alice', age: 30 });
</script>
<button onclick={() => count++}>{count}</button>
Key behaviors:
$state fields inside the class insteadAntipattern - wrapping class instances:
// WRONG: Has no effect
let instance = $state(new MyClass());
// CORRECT: Define $state inside the class
class MyClass {
value = $state(0);
}
Create memoized computations that auto-update:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
let expensive = $derived.by(() => {
// Complex computation
return heavyCalculation(count);
});
</script>
Rules:
$state inside $derived (causes infinite loop)$derived.by() for multi-statement computationsAntipattern - side effects in derived:
// WRONG: Causes infinite loop
let computed = $derived(() => { count++; return count * 2 });
// CORRECT: Keep derived pure
let computed = $derived(count * 2);
Run code in response to state changes:
<script>
let count = $state(0);
$effect(() => {
console.log('Count changed:', count);
// Return cleanup function
return () => {
console.log('Cleanup before next run or unmount');
};
});
</script>
When to use:
Always return cleanup for intervals, listeners, subscriptions:
$effect(() => {
const id = setInterval(() => tick(), 1000);
return () => clearInterval(id);
});
Antipattern - reading and writing same state:
// WRONG: Infinite loop
$effect(() => { count += 1; });
// CORRECT: Use event handlers or separate the logic
Runs before DOM updates (replaces beforeUpdate):
<script>
let messages = $state([]);
let container;
$effect.pre(() => {
// Capture scroll position before DOM update
const wasAtBottom = container.scrollTop + container.clientHeight >= container.scrollHeight;
return () => {
if (wasAtBottom) container.scrollTop = container.scrollHeight;
};
});
</script>
Declare component props with destructuring:
<script>
let { name, age = 18, onclick } = $props();
</script>
<button {onclick}>{name} ({age})</button>
With TypeScript:
<script lang="ts">
interface Props {
name: string;
age?: number;
onclick?: () => void;
}
let { name, age = 18, onclick }: Props = $props();
</script>
Rest props pattern:
<script>
let { class: className, ...rest } = $props();
</script>
<div class={className} {...rest} />
Allow parent components to bind to a prop:
<!-- Child.svelte -->
<script>
let { value = $bindable() } = $props();
</script>
<input bind:value />
<!-- Parent.svelte -->
<script>
let text = $state('');
</script>
<Child bind:value={text} />
Log state changes during development:
<script>
let count = $state(0);
$inspect(count); // Logs on every change
// Custom handler
$inspect(count).with((type, value) => {
if (type === 'update') debugger;
});
</script>
Note: $inspect is stripped in production builds.
Use runes outside components in .svelte.ts files:
// counter.svelte.ts
export function createCounter(initial = 0) {
let count = $state(initial);
return {
get count() { return count },
increment: () => count++,
decrement: () => count--
};
}
Important: Only rename files to .svelte.ts when they directly declare runes. Files importing rune-using functions stay as .ts.
// todo.svelte.ts
export class Todo {
text = $state('');
done = $state(false);
constructor(text: string) {
this.text = text;
}
toggle() {
this.done = !this.done;
}
}
<script>
let { items } = $props();
let total = $derived(items.reduce((sum, i) => sum + i.price, 0));
</script>
<script>
let query = $state('');
let results = $state([]);
$effect(() => {
const controller = new AbortController();
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(r => r.json())
.then(data => results = data);
return () => controller.abort();
});
</script>
| Svelte 4 | Svelte 5 |
|---|---|
let count = 0 (reactive) | let count = $state(0) |
$: doubled = count * 2 | let doubled = $derived(count * 2) |
$: { console.log(count) } | $effect(() => { console.log(count) }) |
export let name | let { name } = $props() |
on:click={handler} | onclick={handler} |
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.