svelte5-runes
Svelte 5 runes syntax and patterns. Use when writing Svelte components, managing state, or working with reactive values. Triggers on Svelte files, component creation, or mentions of $state, $derived,
Files1
View on GitHubSKILL.md.claude/skills/svelte5-runes/SKILL.md
GitHubSvelte 5 Runes Guide
This project uses Svelte 5 with runes. Never use the old store API (writable, readable, derived from 'svelte/store').
Core Runes
$props() - Component Props
<script lang="ts">
// Destructure with types and defaults
let {
title,
count = 0,
disabled = false,
children, // Snippet for slot content
onclick
}: {
title: string;
count?: number;
disabled?: boolean;
children?: Snippet;
onclick?: () => void;
} = $props();
</script>
$state() - Reactive State
<script lang="ts">
// Primitive state
let isOpen = $state(false);
let count = $state(0);
// Array state (deeply reactive)
let items = $state<string[]>([]);
// Object state
let user = $state<User | null>(null);
// Mutate directly - it's reactive!
function addItem(item: string) {
items.push(item); // This triggers updates
}
</script>
$derived() - Computed Values
<script lang="ts">
let items = $state<string[]>([]);
let filter = $state('');
// Simple derived
let count = $derived(items.length);
// Complex derived
let filteredItems = $derived(
items.filter(item => item.includes(filter))
);
// Derived from multiple sources
let summary = $derived(`${count} items, ${filteredItems.length} shown`);
</script>
$effect() - Side Effects
<script lang="ts">
let query = $state('');
// Runs when dependencies change
$effect(() => {
console.log('Query changed:', query);
// Dependencies are auto-tracked
});
// With cleanup (like useEffect return)
$effect(() => {
const controller = new AbortController();
fetchData(query, controller.signal);
return () => controller.abort();
});
// Pre-effect (runs before DOM updates)
$effect.pre(() => {
// Scroll position preservation, etc.
});
</script>
$bindable() - Two-Way Binding Props
<script lang="ts">
// In child component
let { value = $bindable() }: { value?: string } = $props();
</script>
<!-- In parent -->
<Input bind:value={searchQuery} />
Event Handling (Svelte 5 Syntax)
<!-- Use lowercase event handlers -->
<button onclick={handleClick}>Click</button>
<input oninput={(e) => query = e.currentTarget.value} />
<div onmouseenter={handleEnter} onmouseleave={handleLeave}>
Hover me
</div>
<!-- NOT the old on:click syntax -->
Snippets (Replace Slots)
<!-- Define a snippet -->
{#snippet row(item)}
<tr>
<td>{item.name}</td>
<td>{item.value}</td>
</tr>
{/snippet}
<!-- Render it -->
{#each items as item}
{@render row(item)}
{/each}
<!-- Pass as prop -->
<Table {row} {items} />
Global State with Context
// src/lib/state/player.svelte.ts
export function createPlayerState() {
let currentTrack = $state<Track | null>(null);
let isPlaying = $state(false);
let volume = $state(0.8);
return {
get currentTrack() { return currentTrack; },
get isPlaying() { return isPlaying; },
get volume() { return volume; },
play(track: Track) {
currentTrack = track;
isPlaying = true;
},
pause() {
isPlaying = false;
},
setVolume(v: number) {
volume = Math.max(0, Math.min(1, v));
}
};
}
// In +layout.svelte
import { setContext } from 'svelte';
const player = createPlayerState();
setContext('player', player);
// In any component
import { getContext } from 'svelte';
const player = getContext('player');
Migration from Svelte 4
| Svelte 4 | Svelte 5 |
|---|---|
export let prop |
let { prop } = $props() |
let count = 0 (reactive) |
let count = $state(0) |
$: doubled = count * 2 |
let doubled = $derived(count * 2) |
$: console.log(count) |
$effect(() => console.log(count)) |
on:click={handler} |
onclick={handler} |
<slot /> |
{@render children?.()} |
<slot name="x" /> |
{@render x?.()} |
Common Patterns
Debounced Search
<script lang="ts">
let query = $state('');
let debouncedQuery = $state('');
$effect(() => {
const timeout = setTimeout(() => {
debouncedQuery = query;
}, 300);
return () => clearTimeout(timeout);
});
let results = $derived.by(async () => {
if (!debouncedQuery) return [];
return await searchAPI(debouncedQuery);
});
</script>
Toggle with Derived
<script lang="ts">
let isOpen = $state(false);
let buttonText = $derived(isOpen ? 'Close' : 'Open');
let ariaExpanded = $derived(isOpen ? 'true' : 'false');
function toggle() {
isOpen = !isOpen;
}
</script>