references/events.md
# Svelte 5 Events Reference
## Table of Contents
- [Event Handler Syntax](#event-handler-syntax)
- [Callback Props Pattern](#callback-props-pattern)
- [Context API](#context-api)
---
## Event Handler Syntax
Svelte 5 replaces `on:click` directive syntax with standard HTML attribute syntax `onclick`.
### Basic Event Handlers
**Svelte 4:**
```svelte
<button on:click={handleClick}>Click</button>
<input on:input={handleInput} />
<form on:submit={handleSubmit}>...</form>
```
**Svelte 5:**
```svelte
<button onclick={handleClick}>Click</button>
<input oninput={handleInput} />
<form onsubmit={handleSubmit}>...</form>
```
### Event Modifiers Migration
Event modifiers no longer exist. Use wrapper functions:
**Svelte 4:**
```svelte
<form on:submit|preventDefault={handleSubmit}>...</form>
<button on:click|stopPropagation={handleClick}>...</button>
```
**Svelte 5:**
```svelte
<script>
function handleSubmit(event) {
event.preventDefault();
// ... handle form
}
function handleClick(event) {
event.stopPropagation();
// ... handle click
}
</script>
<form onsubmit={handleSubmit}>...</form>
<button onclick={handleClick}>...</button>
```
### Capture, Passive, and NonPassive
```svelte
<!-- Capture phase -->
<div onclickcapture={handleCapture}>
<button onclick={handleClick}>Click</button>
</div>
<!-- Passive listener -->
<div ontouchstartpassive={handleTouch}>...</div>
<!-- Non-passive -->
<div ontouchmovenonpassive={(e) => e.preventDefault()}>...</div>
```
### Inline Handlers
```svelte
<button onclick={() => count++}>Count: {count}</button>
<input oninput={(e) => name = e.target.value} />
```
### Event Handler Shorthand
```svelte
<script>
function onclick(event) {
console.log('Clicked!', event);
}
</script>
<button {onclick}>Click</button>
```
### Spreading Event Handlers
```svelte
<script>
let handlers = {
onclick: () => console.log('clicked'),
onmouseenter: () => console.log('entered'),
onmouseleave: () => console.log('left')
};
</script>
<button {...handlers}>Hover or Click</button>
```
### Multiple Handlers for Same Event
In Svelte 5, combine logic into one handler:
```svelte
<script>
function handleClick(event) {
handler1(event);
handler2(event);
}
</script>
<button onclick={handleClick}>...</button>
```
### TypeScript Event Typing
```svelte
<script lang="ts">
function handleClick(event: MouseEvent) {
console.log(event.clientX, event.clientY);
}
function handleInput(event: Event) {
const target = event.target as HTMLInputElement;
console.log(target.value);
}
function handleSubmit(event: SubmitEvent) {
event.preventDefault();
const formData = new FormData(event.currentTarget as HTMLFormElement);
}
</script>
```
---
## Callback Props Pattern
Svelte 5 deprecates `createEventDispatcher` in favor of callback props for component-to-parent communication.
### Basic Event Pattern
**Svelte 4:**
```svelte
<!-- Button.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
</script>
<button on:click={() => dispatch('click', { timestamp: Date.now() })}>Click</button>
<!-- Parent.svelte -->
<Button on:click={(e) => console.log(e.detail)} />
```
**Svelte 5:**
```svelte
<!-- Button.svelte -->
<script>
let { onclick } = $props();
</script>
<button onclick={() => onclick?.({ timestamp: Date.now() })}>Click</button>
<!-- Parent.svelte -->
<Button onclick={(data) => console.log(data)} />
```
### Multiple Callbacks
```svelte
<!-- Dialog.svelte -->
<script>
let { onconfirm, oncancel, onclose } = $props();
</script>
<dialog>
<button onclick={() => onconfirm?.()}>Confirm</button>
<button onclick={() => oncancel?.()}>Cancel</button>
<button onclick={() => onclose?.()}>X</button>
</dialog>
<!-- Parent.svelte -->
<Dialog
onconfirm={() => save()}
oncancel={() => reset()}
onclose={() => visible = false}
/>
```
### Typed Callbacks with TypeScript
```svelte
<script lang="ts">
interface Props {
value?: string;
onsearch?: (query: string) => void;
onchange?: (value: string) => void;
onclear?: () => void;
}
let { value = '', onsearch, onchange, onclear }: Props = $props();
function handleInput(e: Event) {
const newValue = (e.target as HTMLInputElement).value;
value = newValue;
onchange?.(newValue);
}
</script>
```
### Forwarding Native Events
```svelte
<script lang="ts">
import type { MouseEventHandler } from 'svelte/elements';
interface Props {
onclick?: MouseEventHandler<HTMLButtonElement>;
children: import('svelte').Snippet;
}
let { onclick, children, ...rest }: Props = $props();
</script>
<button {onclick} {...rest}>{@render children()}</button>
```
---
## Context API
Context functions must be called synchronously during component initialization.
### Correct Context Usage
```svelte
<script>
import { setContext, getContext } from 'svelte';
// These run during initialization - CORRECT
setContext('theme', 'dark');
const theme = getContext('theme');
// INCORRECT - in $effect or callback
// $effect(() => { setContext('theme', 'dark'); }); // ERROR
</script>
```
### Reactive Context Values
Pass `$state` objects for reactive context:
```svelte
<!-- Provider.svelte -->
<script>
import { setContext } from 'svelte';
let theme = $state('light');
setContext('theme', {
get current() { return theme; },
toggle() { theme = theme === 'light' ? 'dark' : 'light'; }
});
let { children } = $props();
</script>
{@render children()}
<!-- Consumer.svelte -->
<script>
import { getContext } from 'svelte';
const themeContext = getContext('theme');
</script>
<p>Theme: {themeContext.current}</p>
<button onclick={themeContext.toggle}>Toggle</button>
```
### Type-Safe Context Pattern
```ts
// context.ts
import { setContext, getContext } from 'svelte';
const THEME_KEY = Symbol('theme');
interface ThemeContext {
current: 'light' | 'dark';
toggle: () => void;
}
export function setThemeContext(context: ThemeContext) {
setContext(THEME_KEY, context);
}
export function getThemeContext(): ThemeContext {
const context = getContext<ThemeContext>(THEME_KEY);
if (!context) throw new Error('Theme context not found');
return context;
}
```
### Avoid Generic Keys
```svelte
<script>
// Option 1: Symbol
const USER_KEY = Symbol('user');
setContext(USER_KEY, userData);
// Option 2: Unique string
setContext('myapp:user', userData);
// Option 3: Object key
const userKey = {};
setContext(userKey, userData);
</script>
```
### hasContext Check
```svelte
<script>
import { hasContext, getContext } from 'svelte';
const hasTheme = hasContext('theme');
const theme = hasTheme ? getContext('theme') : { current: 'light' };
</script>
```
### Context Boundaries
Context flows down the component tree:
```svelte
<!-- Parent.svelte -->
<script>
import { getContext, setContext } from 'svelte';
const level = getContext('level'); // 0
setContext('level', level + 1); // Override for children
</script>
<Child /> <!-- Will see level = 1 -->
```
references/migration.md
# Svelte 4 to Svelte 5 Migration Reference
## Table of Contents
- [Migrating Reactive Statements](#migrating-reactive-statements)
- [Migrating Stores to Runes](#migrating-stores-to-runes)
---
## Migrating Reactive Statements
Svelte 5 replaces `$:` with `$derived` (for values) and `$effect` (for side effects).
### Computed Values: $: to $derived
**Svelte 4:**
```svelte
<script>
let count = 0;
$: doubled = count * 2;
$: quadrupled = doubled * 2;
</script>
```
**Svelte 5:**
```svelte
<script>
let count = $state(0);
let doubled = $derived(count * 2);
let quadrupled = $derived(doubled * 2);
</script>
```
### Complex Computed Values: $derived.by
**Svelte 4:**
```svelte
<script>
let items = [];
let filter = 'all';
$: filteredItems = {
if (filter === 'all') return items;
if (filter === 'active') return items.filter(i => !i.done);
return items.filter(i => i.done);
};
</script>
```
**Svelte 5:**
```svelte
<script>
let items = $state([]);
let filter = $state('all');
let filteredItems = $derived.by(() => {
if (filter === 'all') return items;
if (filter === 'active') return items.filter(i => !i.done);
return items.filter(i => i.done);
});
</script>
```
### Side Effects: $: to $effect
**Svelte 4:**
```svelte
<script>
let count = 0;
$: console.log('Count changed:', count);
$: document.title = `Count: ${count}`;
</script>
```
**Svelte 5:**
```svelte
<script>
let count = $state(0);
$effect(() => { console.log('Count changed:', count); });
$effect(() => { document.title = `Count: ${count}`; });
</script>
```
### Conditional Side Effects
**Svelte 4:**
```svelte
<script>
let value;
$: if (value > 100) { alert('Value too high!'); }
</script>
```
**Svelte 5:**
```svelte
<script>
let value = $state(0);
$effect(() => {
if (value > 100) { alert('Value too high!'); }
});
</script>
```
### Props Migration
**Svelte 4:**
```svelte
<script>
export let name;
export let count = 0;
$: greeting = `Hello, ${name}!`;
</script>
```
**Svelte 5:**
```svelte
<script>
let { name, count = 0 } = $props();
let greeting = $derived(`Hello, ${name}!`);
</script>
```
### Effect with Cleanup
**Svelte 4:**
```svelte
<script>
import { onDestroy } from 'svelte';
let count = 0;
let interval;
$: {
clearInterval(interval);
interval = setInterval(() => console.log(count), 1000);
}
onDestroy(() => clearInterval(interval));
</script>
```
**Svelte 5:**
```svelte
<script>
let count = $state(0);
$effect(() => {
const interval = setInterval(() => console.log(count), 1000);
return () => clearInterval(interval); // Cleanup built-in
});
</script>
```
### Migration Cheat Sheet
| Svelte 4 Pattern | Svelte 5 Replacement |
|------------------|----------------------|
| `let x = 0` (in component) | `let x = $state(0)` |
| `export let prop` | `let { prop } = $props()` |
| `$: derived = expr` | `let derived = $derived(expr)` |
| `$: { complex }` (value) | `let x = $derived.by(() => { ... })` |
| `$: console.log(x)` | `$effect(() => console.log(x))` |
| `$: if (x) { ... }` | `$effect(() => { if (x) { ... } })` |
| `$: document.title = x` | `$effect(() => { document.title = x })` |
### Automated Migration
```bash
npx sv migrate svelte-5
```
---
## Migrating Stores to Runes
Svelte 5 runes can replace most store use cases with simpler, more direct reactive state.
### Local Component State
**Svelte 4:**
```svelte
<script>
import { writable } from 'svelte/store';
const count = writable(0);
function increment() { count.update(n => n + 1); }
</script>
<button on:click={increment}>Count: {$count}</button>
```
**Svelte 5:**
```svelte
<script>
let count = $state(0);
function increment() { count++; }
</script>
<button onclick={increment}>Count: {count}</button>
```
### Shared State Across Components
**Svelte 4 (stores.ts):**
```ts
import { writable } from 'svelte/store';
export const user = writable(null);
export const theme = writable('light');
```
**Svelte 5 (state.svelte.ts):**
```ts
export const user = $state<User | null>(null);
export const theme = $state({ current: 'light' as 'light' | 'dark' });
export function setTheme(newTheme: 'light' | 'dark') {
theme.current = newTheme;
}
```
```svelte
<script>
import { theme, setTheme } from './state.svelte';
</script>
<p>Theme: {theme.current}</p>
<button onclick={() => setTheme('dark')}>Dark Mode</button>
```
### Derived Store to $derived
**Svelte 4:**
```ts
import { writable, derived } from 'svelte/store';
export const items = writable([]);
export const completedCount = derived(items, $items =>
$items.filter(i => i.done).length
);
```
**Svelte 5:**
```ts
// state.svelte.ts
export const items = $state<Item[]>([]);
export function getCompletedCount() {
return items.filter(i => i.done).length;
}
```
```svelte
<script>
import { items } from './state.svelte';
let completedCount = $derived(items.filter(i => i.done).length);
</script>
```
### Custom Store to Reactive Class
**Svelte 4:**
```ts
function createCounter() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
export const counter = createCounter();
```
**Svelte 5:**
```ts
// counter.svelte.ts
class Counter {
value = $state(0);
increment() { this.value++; }
decrement() { this.value--; }
reset() { this.value = 0; }
}
export const counter = new Counter();
```
```svelte
<script>
import { counter } from './counter.svelte';
</script>
<p>{counter.value}</p>
<button onclick={() => counter.increment()}>+</button>
```
### Async State Pattern
**Svelte 5:**
```ts
// api.svelte.ts
export const state = $state({
data: null as Data | null,
loading: false,
error: null as Error | null
});
export async function fetchData() {
state.loading = true;
state.error = null;
try {
const res = await fetch('/api/data');
state.data = await res.json();
} catch (e) {
state.error = e as Error;
} finally {
state.loading = false;
}
}
```
### When to Still Use Stores
1. **Library interop** - Libraries that expect Svelte stores
2. **Legacy code** - Gradual migration
3. **Observable patterns** - When you need the subscribe API
4. **Server-side rendering** - Stores have built-in SSR handling
### Store to Rune Cheat Sheet
| Store Pattern | Rune Replacement |
|---------------|------------------|
| `writable(value)` | `$state(value)` |
| `$store` (auto-subscribe) | Direct access to `$state` value |
| `store.set(x)` | `state = x` |
| `store.update(fn)` | Direct mutation or reassignment |
| `derived(store, fn)` | `$derived(fn())` in component |
| `readable(value, start)` | `$state` + `$effect` for setup |
references/performance.md
# Svelte 5 Performance Reference
## Table of Contents
- [Universal Reactivity](#universal-reactivity)
- [Avoiding Over-Reactivity](#avoiding-over-reactivity)
- [Preventing Load Waterfalls](#preventing-load-waterfalls)
- [Streaming Non-Critical Data](#streaming-non-critical-data)
- [Component Testing](#component-testing)
---
## Universal Reactivity
Svelte 5 allows runes in `.svelte.js` or `.svelte.ts` files for shared state outside components.
### Shared Counter State
```ts
// counter.svelte.ts
export const counter = $state({ count: 0 });
export function increment() { counter.count++; }
export function decrement() { counter.count--; }
export function reset() { counter.count = 0; }
```
```svelte
<script>
import { counter, increment } from './counter.svelte';
</script>
<p>Count: {counter.count}</p>
<button onclick={increment}>+</button>
```
### Object State with Getters
```ts
// user.svelte.ts
const state = $state<User>({ firstName: '', lastName: '', email: '' });
export const user = {
get firstName() { return state.firstName; },
set firstName(v: string) { state.firstName = v; },
get fullName() { return `${state.firstName} ${state.lastName}`; },
get isValid() { return state.firstName && state.email.includes('@'); }
};
```
### Reactive Class Pattern
```ts
// todo.svelte.ts
class TodoStore {
items = $state<Todo[]>([]);
filter = $state<'all' | 'active' | 'completed'>('all');
get filtered() {
switch (this.filter) {
case 'active': return this.items.filter(t => !t.done);
case 'completed': return this.items.filter(t => t.done);
default: return this.items;
}
}
add(text: string) {
this.items.push({ id: Date.now(), text, done: false });
}
}
export const todos = new TodoStore();
```
### Important Notes
1. **File extension matters**: Must use `.svelte.js` or `.svelte.ts`
2. **No $derived in module scope**: Use getters for derived values
3. **SSR caution**: Avoid initializing browser-only state at module level
---
## Avoiding Over-Reactivity
Common mistakes with runes can cause unnecessary re-renders, infinite loops, or degraded performance.
### Anti-Pattern 1: Using $effect to Set Derived Values
**WRONG:**
```svelte
<script>
let count = $state(0);
let doubled = $state(0);
$effect(() => { doubled = count * 2; }); // Anti-pattern!
</script>
```
**CORRECT:**
```svelte
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
```
### Anti-Pattern 2: Circular Dependencies
**WRONG:**
```svelte
<script>
let a = $state(1);
let b = $state(2);
$effect(() => {
if (a > 10) b = 0;
if (b > 10) a = 0; // Triggers the effect again!
});
</script>
```
**CORRECT: Use separate effects or event handlers**
### Anti-Pattern 3: Not Using untrack
**WRONG:**
```svelte
<script>
let count = $state(0);
let log = $state([]);
$effect(() => {
log.push(`Count is ${count}`); // log change triggers re-run!
});
</script>
```
**CORRECT:**
```svelte
<script>
import { untrack } from 'svelte';
let count = $state(0);
let log = $state([]);
$effect(() => {
untrack(() => { log.push(`Count is ${count}`); });
});
</script>
```
### Anti-Pattern 4: Heavy Computations in $derived
**WRONG:**
```svelte
<script>
let items = $state([/* thousands */]);
let filter = $state('');
let filtered = $derived(
items.filter(item => JSON.stringify(item).includes(filter))
);
</script>
```
**CORRECT: Debounce expensive computations**
```svelte
<script>
let filter = $state('');
let debouncedFilter = $state('');
$effect(() => {
const timeout = setTimeout(() => { debouncedFilter = filter; }, 300);
return () => clearTimeout(timeout);
});
let filtered = $derived(
items.filter(item => item.name.includes(debouncedFilter))
);
</script>
```
### Anti-Pattern 5: Effect for DOM Manipulation
**WRONG:**
```svelte
<script>
let visible = $state(false);
let element;
$effect(() => {
if (element) element.style.display = visible ? 'block' : 'none';
});
</script>
```
**CORRECT:**
```svelte
{#if visible}<div>Content</div>{/if}
<!-- Or -->
<div class:hidden={!visible}>Content</div>
```
### Performance Tips
1. Use `$derived` over `$effect` for computed values
2. Debounce expensive reactive computations
3. Use `untrack` to prevent unnecessary dependencies
4. Group related state into objects
5. Let Svelte handle DOM updates
---
## Preventing Load Waterfalls
Sequential API calls multiply latency. Use parallel requests and streaming.
### Anti-Pattern: Waterfall
**WRONG (3 seconds total):**
```ts
export const load = async ({ fetch }) => {
const user = await fetch('/api/user').then(r => r.json()); // 1s
const posts = await fetch(`/api/users/${user.id}/posts`).then(r => r.json()); // 1s
const comments = await fetch('/api/comments').then(r => r.json()); // 1s
return { user, posts, comments };
};
```
### Pattern 1: Parallel with Promise.all
**CORRECT (1 second total):**
```ts
export const load = async ({ fetch }) => {
const [user, posts, comments] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { user, posts, comments };
};
```
### Pattern 2: Partial Dependencies
```ts
export const load = async ({ fetch }) => {
const user = await fetch('/api/user').then(r => r.json());
const [posts, followers] = await Promise.all([
fetch(`/api/users/${user.id}/posts`).then(r => r.json()),
fetch(`/api/users/${user.id}/followers`).then(r => r.json())
]);
return { user, posts, followers };
};
```
### Pattern 3: Streaming Non-Critical Data
```ts
export const load = async ({ fetch }) => {
const user = await fetch('/api/user').then(r => r.json());
return {
user,
recommendations: fetch('/api/recommendations').then(r => r.json()),
analytics: fetch('/api/analytics').then(r => r.json())
};
};
```
### Performance Comparison
| Pattern | 3 APIs x 1s each |
|---------|------------------|
| Sequential | 3 seconds |
| Parallel (Promise.all) | 1 second |
| Streaming | 0s initial, streams rest |
---
## Streaming Non-Critical Data
Return promises from load functions to stream non-essential data after initial page render.
### Basic Streaming
**WRONG (blocks on slow data):**
```ts
export const load = async ({ fetch }) => {
const user = await fetch('/api/user').then(r => r.json()); // 100ms
const analytics = await fetch('/api/analytics').then(r => r.json()); // 2000ms
return { user, analytics }; // Page blocked for 2.1 seconds
};
```
**CORRECT (stream slow data):**
```ts
export const load = async ({ fetch }) => {
const user = await fetch('/api/user').then(r => r.json());
const analytics = fetch('/api/analytics').then(r => r.json()); // Don't await
return {
user, // Available in 100ms
analytics // Streams when ready
};
};
```
### Component Handling
```svelte
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<header><h1>Welcome, {data.user.name}</h1></header>
<aside>
{#await data.analytics}
<div class="skeleton">Loading analytics...</div>
{:then analytics}
<div class="analytics">
<p>Page views: {analytics.views}</p>
</div>
{:catch}
<div class="error">Failed to load analytics</div>
{/await}
</aside>
```
### When to Stream
| Data Type | Stream? | Reason |
|-----------|---------|--------|
| User info | No | Critical for layout |
| Main content | No | Users came for this |
| Analytics | Yes | Not user-facing |
| Recommendations | Yes | Supplementary |
| Comments | Maybe | Important but can load later |
---
## Component Testing
Svelte 5 components with runes require proper Vitest configuration.
### Option 1: Vitest Browser Mode (Recommended)
```ts
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
plugins: [svelte()],
test: {
browser: {
enabled: true,
provider: 'playwright',
name: 'chromium'
}
}
});
```
### Option 2: JSDOM with @testing-library/svelte
```ts
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
plugins: [svelte({ hot: !process.env.VITEST })],
test: {
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,ts}'],
globals: true
}
});
```
### Basic Component Test
```ts
import { render, screen, fireEvent } from '@testing-library/svelte';
import { expect, test } from 'vitest';
import Counter from './Counter.svelte';
test('increments count when clicked', async () => {
render(Counter);
const button = screen.getByRole('button');
expect(button).toHaveTextContent('Count: 0');
await fireEvent.click(button);
expect(button).toHaveTextContent('Count: 1');
});
```
### Testing Props
```ts
test('renders with custom name', () => {
render(Greeting, { props: { name: 'Alice' } });
expect(screen.getByText('Hello, Alice!')).toBeInTheDocument();
});
```
### Testing Callbacks
```ts
import { vi } from 'vitest';
test('calls onclick callback when clicked', async () => {
const handleClick = vi.fn();
render(Button, { props: { onclick: handleClick } });
await fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
```
### Testing Async Components
```ts
import { waitFor } from '@testing-library/svelte';
test('loads and displays user data', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ name: 'Alice' })
});
render(UserProfile, { props: { userId: '123' } });
expect(screen.getByText('Loading...')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('Alice')).toBeInTheDocument();
});
});
```
references/runes.md
# Svelte 5 Runes Reference
## Table of Contents
- [$state - Reactive State](#state---reactive-state)
- [$derived - Computed Values](#derived---computed-values)
- [$effect - Side Effects](#effect---side-effects)
- [$props - Component Props](#props---component-props)
- [$bindable - Two-Way Binding](#bindable---two-way-binding)
- [$inspect - Debugging](#inspect---debugging)
---
## $state - Reactive State
In Svelte 5, plain `let` declarations are no longer automatically reactive. Use `$state()` for reactive state.
### Basic Usage
```svelte
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
Clicks: {count}
</button>
```
### Object and Array State
Objects and arrays are deeply reactive by default:
```svelte
<script>
let user = $state({ name: 'Alice', age: 30 });
let items = $state(['apple', 'banana']);
</script>
<button onclick={() => user.age++}>Age: {user.age}</button>
<button onclick={() => items.push('cherry')}>Items: {items.length}</button>
```
### Class State
```svelte
<script>
class Counter {
count = $state(0);
increment() { this.count++; }
}
const counter = new Counter();
</script>
<button onclick={() => counter.increment()}>{counter.count}</button>
```
### Raw State (Opt-out of Deep Reactivity)
```svelte
<script>
let items = $state.raw([1, 2, 3]);
// This WON'T trigger an update:
items.push(4);
// This WILL trigger an update:
items = [...items, 4];
</script>
```
---
## $derived - Computed Values
Replaces `$:` reactive statements for computed values.
### Basic Usage
```svelte
<script>
let count = $state(0);
let doubled = $derived(count * 2);
let quadrupled = $derived(doubled * 2);
</script>
```
### Complex Derivations with $derived.by
```svelte
<script>
let items = $state([1, 2, 3, 4, 5]);
let filter = $state('even');
let filteredItems = $derived.by(() => {
if (filter === 'even') return items.filter(n => n % 2 === 0);
return items.filter(n => n % 2 !== 0);
});
</script>
```
### Deriving from Props
```svelte
<script>
let { firstName, lastName } = $props();
let fullName = $derived(`${firstName} ${lastName}`);
</script>
```
### $derived vs $effect
Use `$derived` for computing values, `$effect` for side effects:
```svelte
<script>
let count = $state(0);
// CORRECT: Use $derived for computed values
let doubled = $derived(count * 2);
// INCORRECT: Don't use $effect to set derived values
// let doubled;
// $effect(() => { doubled = count * 2; }); // Anti-pattern!
</script>
```
---
## $effect - Side Effects
Runs code when component mounts and when dependencies change. Requires cleanup for subscriptions.
### Basic Effect with Cleanup
```svelte
<script>
let count = $state(0);
$effect(() => {
const interval = setInterval(() => count++, 1000);
return () => clearInterval(interval); // Cleanup
});
</script>
```
### DOM Event Listeners
```svelte
<script>
let mouseX = $state(0);
let mouseY = $state(0);
$effect(() => {
function handleMouseMove(event) {
mouseX = event.clientX;
mouseY = event.clientY;
}
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
});
</script>
```
### External Subscriptions
```svelte
<script>
let { userId } = $props();
let userData = $state(null);
$effect(() => {
const unsubscribe = database.subscribe(`users/${userId}`, (data) => {
userData = data;
});
return () => unsubscribe();
});
</script>
```
### $effect.pre for Pre-DOM Updates
```svelte
<script>
let div;
let messages = $state([]);
$effect.pre(() => {
if (div) {
const shouldScroll = div.scrollTop + div.clientHeight >= div.scrollHeight - 20;
if (shouldScroll) {
tick().then(() => { div.scrollTop = div.scrollHeight; });
}
}
});
</script>
```
### Untracked Dependencies
```svelte
<script>
import { untrack } from 'svelte';
let count = $state(0);
let logCount = $state(0);
$effect(() => {
// Only runs when count changes, not logCount
console.log(count, untrack(() => logCount));
});
</script>
```
---
## $props - Component Props
Replaces `export let` for declaring component props.
### Basic Usage
```svelte
<script>
let { name, count = 0, disabled = false } = $props();
</script>
```
### Rest Props Pattern
```svelte
<script>
let { class: className, children, ...restProps } = $props();
</script>
<div class={className} {...restProps}>
{@render children?.()}
</div>
```
### Renaming Reserved Words
```svelte
<script>
let {
class: className, // 'class' is reserved
for: htmlFor, // 'for' is reserved
...rest
} = $props();
</script>
```
### TypeScript Props
```svelte
<script lang="ts">
interface Props {
name: string;
count?: number;
onClick?: (event: MouseEvent) => void;
}
let { name, count = 0, onClick }: Props = $props();
</script>
```
### Props with Children
```svelte
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
title: string;
children: Snippet;
footer?: Snippet;
}
let { title, children, footer }: Props = $props();
</script>
<article>
<h1>{title}</h1>
<main>{@render children()}</main>
{#if footer}<footer>{@render footer()}</footer>{/if}
</article>
```
---
## $bindable - Two-Way Binding
Props must be explicitly marked with `$bindable()` to support `bind:`.
### Basic Usage
```svelte
<!-- Input.svelte -->
<script>
let { value = $bindable() } = $props();
</script>
<input bind:value />
<!-- Parent.svelte -->
<script>
import Input from './Input.svelte';
let name = $state('');
</script>
<Input bind:value={name} />
```
### Bindable with Default Value
```svelte
<script>
let { value = $bindable('default') } = $props();
</script>
```
### Multiple Bindable Props
```svelte
<script>
let {
value = $bindable(''),
checked = $bindable(false),
selected = $bindable(null)
} = $props();
</script>
```
### TypeScript with Bindable
```svelte
<script lang="ts">
interface Props {
value: string;
disabled?: boolean;
}
let { value = $bindable(''), disabled = false }: Props = $props();
</script>
```
---
## $inspect - Debugging
Development-only debugging that logs values when they change. Stripped in production.
### Basic Usage
```svelte
<script>
let count = $state(0);
$inspect(count); // Logs every time count changes
</script>
```
### Multiple Values
```svelte
<script>
let name = $state('Alice');
let age = $state(30);
$inspect(name, age);
</script>
```
### Custom Logging with $inspect.with
```svelte
<script>
let user = $state({ name: 'Alice', age: 30 });
$inspect.with((type, ...values) => {
if (type === 'init') console.log('Initial value:', values);
else console.log('Updated to:', values);
}, user);
</script>
```
### Debugging with Breakpoints
```svelte
<script>
let data = $state({ x: 0, y: 0 });
$inspect.with((type, value) => {
if (type === 'update' && value.x > 100) debugger;
}, data);
</script>
```
references/snippets.md
# Svelte 5 Snippets Reference
## Table of Contents
- [Replacing Slots with Snippets](#replacing-slots-with-snippets)
- [Rendering with @render](#rendering-with-render)
---
## Replacing Slots with Snippets
Svelte 5 replaces `<slot>` with snippets - a more powerful and type-safe composition primitive.
### Default Content (children)
**Svelte 4:**
```svelte
<div class="card"><slot /></div>
```
**Svelte 5:**
```svelte
<script>
let { children } = $props();
</script>
<div class="card">{@render children?.()}</div>
```
### Named Slots to Named Snippets
**Svelte 4:**
```svelte
<header><slot name="header" /></header>
<main><slot /></main>
<footer><slot name="footer" /></footer>
<!-- Usage -->
<Layout>
<h1 slot="header">Title</h1>
<p>Content</p>
<span slot="footer">Footer</span>
</Layout>
```
**Svelte 5:**
```svelte
<script>
let { header, children, footer } = $props();
</script>
<header>{@render header?.()}</header>
<main>{@render children?.()}</main>
<footer>{@render footer?.()}</footer>
<!-- Usage -->
<Layout>
{#snippet header()}<h1>Title</h1>{/snippet}
<p>Content</p>
{#snippet footer()}<span>Footer</span>{/snippet}
</Layout>
```
### Slot Props to Snippet Parameters
**Svelte 4:**
```svelte
<ul>
{#each items as item}
<li><slot {item} /></li>
{/each}
</ul>
<List {items} let:item>
<span>{item.name}</span>
</List>
```
**Svelte 5:**
```svelte
<script>
let { items, children } = $props();
</script>
<ul>
{#each items as item}
<li>{@render children?.(item)}</li>
{/each}
</ul>
<List {items}>
{#snippet children(item)}
<span>{item.name}</span>
{/snippet}
</List>
```
### Slot Fallback to Snippet Fallback
```svelte
<script>
let { children } = $props();
</script>
{#if children}
{@render children()}
{:else}
<p>Default content</p>
{/if}
```
### Defining Snippets Within Components
```svelte
<script>
let items = $state([
{ id: 1, name: 'Item 1', type: 'normal' },
{ id: 2, name: 'Item 2', type: 'featured' }
]);
</script>
{#snippet normalItem(item)}
<li>{item.name}</li>
{/snippet}
{#snippet featuredItem(item)}
<li class="featured">*** {item.name} ***</li>
{/snippet}
<ul>
{#each items as item}
{#if item.type === 'featured'}
{@render featuredItem(item)}
{:else}
{@render normalItem(item)}
{/if}
{/each}
</ul>
```
### TypeScript Typing
```svelte
<script lang="ts">
import type { Snippet } from 'svelte';
interface Item { id: number; name: string; }
interface Props {
header?: Snippet;
children: Snippet<[item: Item]>;
footer?: Snippet;
}
let { header, children, footer }: Props = $props();
</script>
```
---
## Rendering with @render
Snippets must be rendered using `{@render}`. Optional snippets need null-safe calling.
### Basic Snippet Rendering
```svelte
<script>
let { children } = $props();
</script>
<div>{@render children()}</div>
```
### Optional Snippets
Always use optional chaining for snippets that might not be provided:
```svelte
<script>
let { children } = $props();
</script>
{@render children?.()}
```
### Conditional Rendering
```svelte
<script>
let { header, children, footer } = $props();
</script>
{#if header}
<header>{@render header()}</header>
{/if}
<main>{@render children?.()}</main>
{#if footer}
<footer>{@render footer()}</footer>
{/if}
```
### Rendering with Arguments
```svelte
<script>
let { items, itemTemplate } = $props();
</script>
<ul>
{#each items as item, index}
<li>{@render itemTemplate(item, index)}</li>
{/each}
</ul>
<!-- Usage -->
<List {items}>
{#snippet itemTemplate(item, index)}
<span>{index + 1}. {item.name}</span>
{/snippet}
</List>
```
### Rendering Multiple Times
Snippets can be rendered multiple times:
```svelte
<script>
let { icon } = $props();
</script>
<button>
{@render icon?.()}
<span>Click me</span>
{@render icon?.()}
</button>
```
### Rendering Dynamic Snippets
```svelte
<script>
let { normalView, editView, isEditing } = $props();
let currentView = $derived(isEditing ? editView : normalView);
</script>
{@render currentView?.()}
```
### TypeScript with @render
```svelte
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
children: Snippet;
header?: Snippet;
row: Snippet<[data: { id: number; name: string }]>;
}
let { children, header, row }: Props = $props();
</script>
{@render header?.()}
{@render children()}
{@render row({ id: 1, name: 'Test' })}
```
### Passing Snippets to Child Components
```svelte
<!-- Wrapper.svelte -->
<script>
import Inner from './Inner.svelte';
let { itemRenderer } = $props();
</script>
<Inner {itemRenderer} />
<!-- Inner.svelte -->
<script>
let { itemRenderer } = $props();
</script>
{#each items as item}
{@render itemRenderer?.(item)}
{/each}
```
references/sveltekit.md
# SvelteKit Patterns Reference
## Table of Contents
- [Load Function Types](#load-function-types)
- [Page Props Typing](#page-props-typing)
- [Form Actions Error Handling](#form-actions-error-handling)
- [SSR State Isolation](#ssr-state-isolation)
---
## Load Function Types
SvelteKit has two load function types: universal (`+page.js`) and server-only (`+page.server.ts`).
### When to Use Each
| Use +page.server.ts | Use +page.js |
|---------------------|--------------|
| Accessing secrets/credentials | Public APIs only |
| Database connections | Non-serializable data (functions, classes) |
| Server-only APIs | Client-side caching benefits |
| Sensitive business logic | |
### Security: Secrets in Server Load
**WRONG (secrets exposed):**
```ts
// +page.js - DANGEROUS: runs in browser!
export const load = async ({ fetch }) => {
const response = await fetch('https://api.stripe.com/charges', {
headers: { 'Authorization': `Bearer ${STRIPE_SECRET_KEY}` } // EXPOSED!
});
return { charges: await response.json() };
};
```
**CORRECT:**
```ts
// +page.server.ts - only runs on server
import { STRIPE_SECRET_KEY } from '$env/static/private';
export const load = async ({ fetch }) => {
const response = await fetch('https://api.stripe.com/charges', {
headers: { 'Authorization': `Bearer ${STRIPE_SECRET_KEY}` }
});
return { charges: await response.json() };
};
```
### Serialization: Non-serializable in Universal Load
**WRONG (server load can't serialize functions):**
```ts
// +page.server.ts
export const load = async () => {
return {
formatDate: (date: Date) => date.toLocaleDateString(), // ERROR
parser: new DOMParser() // ERROR
};
};
```
**CORRECT:**
```ts
// +page.js
export const load = async () => {
return {
formatDate: (date: Date) => date.toLocaleDateString(), // OK
parser: typeof window !== 'undefined' ? new DOMParser() : null
};
};
```
### Database Access
```ts
// +page.server.ts - CORRECT
import { db } from '$lib/server/database';
export const load = async () => {
const users = await db.query('SELECT * FROM users');
return { users };
};
```
### Environment Variables
```ts
// +page.server.ts - Private env vars
import { DATABASE_URL, API_SECRET } from '$env/static/private';
// +page.js - Only public env vars
import { PUBLIC_API_URL } from '$env/static/public';
```
---
## Page Props Typing
SvelteKit generates types in `./$types` for full type safety with `$props()`.
### Basic Page Data Typing
```svelte
<!-- +page.svelte -->
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<h1>{data.title}</h1>
```
### Page Data with Form Actions
```svelte
<script lang="ts">
import type { PageProps } from './$types';
let { data, form }: PageProps = $props();
</script>
{#if form?.success}
<p class="success">{form.message}</p>
{/if}
{#if form?.error}
<p class="error">{form.error}</p>
{/if}
```
### Layout Data Typing
```svelte
<!-- +layout.svelte -->
<script lang="ts">
import type { LayoutProps } from './$types';
let { data, children }: LayoutProps = $props();
</script>
<nav>
{#if data.user}<span>Welcome, {data.user.name}</span>{/if}
</nav>
{@render children()}
```
### Corresponding Load Function
```ts
// +page.server.ts
import type { PageServerLoad, Actions } from './$types';
export const load: PageServerLoad = async () => {
return {
title: 'My Page',
items: await fetchItems()
};
};
export const actions: Actions = {
default: async ({ request }) => {
return { success: true, message: 'Saved successfully' };
}
};
```
### Error Page
```svelte
<!-- +error.svelte -->
<script lang="ts">
import { page } from '$app/state';
</script>
<h1>{page.status}: {page.error?.message}</h1>
```
---
## Form Actions Error Handling
SvelteKit distinguishes between validation errors (`fail()`) and unexpected errors (`throw error()`).
### Use fail() for Validation Errors
**WRONG:**
```ts
import { error } from '@sveltejs/kit';
export const actions = {
default: async ({ request }) => {
if (!email?.toString().includes('@')) {
throw error(400, 'Invalid email'); // Shows error page!
}
}
};
```
**CORRECT:**
```ts
import { fail } from '@sveltejs/kit';
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const email = data.get('email')?.toString() ?? '';
if (!email.includes('@')) {
return fail(400, {
error: 'Invalid email address',
email // Return for repopulation
});
}
return { success: true };
}
};
```
### Use throw error() for Unexpected Errors
```ts
import { fail, error } from '@sveltejs/kit';
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
// Validation - use fail()
if (!data.get('title')) {
return fail(400, { error: 'Title is required' });
}
try {
await database.save(data);
return { success: true };
} catch (e) {
// Unexpected error - use throw
throw error(500, 'Unable to save. Please try again later.');
}
}
};
```
### Structured Validation Responses
```ts
interface FormErrors {
email?: string;
password?: string;
}
export const actions = {
register: async ({ request }) => {
const data = await request.formData();
const errors: FormErrors = {};
if (!email) errors.email = 'Email is required';
if (password.length < 8) errors.password = 'Password must be at least 8 characters';
if (Object.keys(errors).length > 0) {
return fail(400, { errors, values: { email } });
}
return { success: true };
}
};
```
### Handle in Component
```svelte
<script lang="ts">
import type { PageProps } from './$types';
import { enhance } from '$app/forms';
let { form }: PageProps = $props();
</script>
<form method="POST" action="?/register" use:enhance>
<label>
Email
<input name="email" value={form?.values?.email ?? ''} />
{#if form?.errors?.email}
<span class="error">{form.errors.email}</span>
{/if}
</label>
<button type="submit">Register</button>
</form>
```
### Error Response Summary
| Situation | Function | Result |
|-----------|----------|--------|
| Missing field | `fail(400, {...})` | Form state preserved |
| Invalid format | `fail(400, {...})` | Form state preserved |
| Not found | `throw error(404, ...)` | Error page |
| Server crash | `throw error(500, ...)` | Error page |
| Auth required | `throw redirect(303, ...)` | Redirect |
---
## SSR State Isolation
Shared server state persists across requests, potentially leaking data between users.
### Dangerous: Module-Level State
**WRONG:**
```ts
// +page.server.ts
let currentUser = null; // SHARED ACROSS ALL REQUESTS!
export const load = async ({ locals }) => {
currentUser = locals.user; // User B overwrites User A
return { user: currentUser };
};
```
**CORRECT:**
```ts
export const load = async ({ locals }) => {
return { user: locals.user }; // Each request gets its own locals
};
```
### Dangerous: Global Stores
**WRONG:**
```ts
// stores.svelte.ts
export const user = $state<User | null>(null); // Server-side singleton!
```
**CORRECT: Use locals and return data from load**
```ts
// +layout.server.ts
export const load = async ({ locals }) => {
return { user: locals.user };
};
```
### Safe Patterns
**Pattern 1: Use locals**
```ts
// hooks.server.ts
export const handle = async ({ event, resolve }) => {
event.locals.user = await authenticate(event);
event.locals.requestId = crypto.randomUUID();
return resolve(event);
};
// +page.server.ts
export const load = async ({ locals }) => {
return { user: locals.user };
};
```
**Pattern 2: Context for Component Trees**
```svelte
<!-- +layout.svelte -->
<script lang="ts">
import { setContext } from 'svelte';
import type { LayoutProps } from './$types';
let { data, children }: LayoutProps = $props();
setContext('user', {
get current() { return data.user; }
});
</script>
{@render children()}
```
**Pattern 3: Client-Only State**
```ts
// stores.svelte.ts
import { browser } from '$app/environment';
function createClientStore() {
if (!browser) return { value: null };
const state = $state({ value: null });
return state;
}
export const clientState = createClientStore();
```
### SSR Safety Checklist
- [ ] No module-level `let` variables that store user data
- [ ] No global `$state` that gets set during SSR
- [ ] No singleton service classes with mutable state
- [ ] All user-specific data flows through `locals`
- [ ] All page data comes from load function returns
references/typescript.md
# Svelte 5 TypeScript Reference
## Table of Contents
- [Props Typing](#props-typing)
- [Generic Components](#generic-components)
---
## Props Typing
`$props()` requires specific patterns for TypeScript typing.
### Basic Props Typing
**Inline typing:**
```svelte
<script lang="ts">
let { name, count = 0 }: { name: string; count?: number } = $props();
</script>
```
**Interface typing (recommended):**
```svelte
<script lang="ts">
interface Props {
name: string;
count?: number;
disabled?: boolean;
}
let { name, count = 0, disabled = false }: Props = $props();
</script>
```
### Children and Snippets Typing
```svelte
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
children: Snippet; // Required children
header?: Snippet; // Optional snippet
row: Snippet<[data: RowData]>; // Snippet with parameter
cell?: Snippet<[value: string, index: number]>; // Multiple params
}
interface RowData { id: number; name: string; }
let { children, header, row, cell }: Props = $props();
</script>
```
### Callback Props Typing
```svelte
<script lang="ts">
interface Props {
value: string;
onchange?: (value: string) => void;
onsubmit?: (data: FormData) => Promise<void>;
onclick?: (event: MouseEvent) => void;
}
let { value, onchange, onsubmit, onclick }: Props = $props();
</script>
```
### Rest Props with HTML Attributes
```svelte
<script lang="ts">
import type { HTMLButtonAttributes } from 'svelte/elements';
interface Props extends HTMLButtonAttributes {
variant?: 'primary' | 'secondary';
loading?: boolean;
}
let { variant = 'primary', loading = false, ...rest }: Props = $props();
</script>
<button class={variant} disabled={loading} {...rest}>
{#if loading}Loading...{:else}{@render children?.()}{/if}
</button>
```
### Input Element Props
```svelte
<script lang="ts">
import type { HTMLInputAttributes } from 'svelte/elements';
interface Props extends Omit<HTMLInputAttributes, 'value'> {
value?: string;
label: string;
error?: string;
}
let { value = $bindable(''), label, error, ...rest }: Props = $props();
</script>
```
### Union Type Props
```svelte
<script lang="ts">
type ButtonVariant = 'primary' | 'secondary' | 'danger';
type ButtonSize = 'sm' | 'md' | 'lg';
interface Props {
variant?: ButtonVariant;
size?: ButtonSize;
children: Snippet;
}
let { variant = 'primary', size = 'md', children }: Props = $props();
</script>
<button class="{variant} {size}">{@render children()}</button>
```
### Discriminated Union Props
```svelte
<script lang="ts">
type Props =
| { type: 'link'; href: string; children: Snippet }
| { type: 'button'; onclick: () => void; children: Snippet };
let props: Props = $props();
</script>
{#if props.type === 'link'}
<a href={props.href}>{@render props.children()}</a>
{:else}
<button onclick={props.onclick}>{@render props.children()}</button>
{/if}
```
---
## Generic Components
Svelte 5 uses the `generics` attribute for type-safe reusable components.
### Basic Generic Component
```svelte
<!-- List.svelte -->
<script lang="ts" generics="T">
interface Props {
items: T[];
children: import('svelte').Snippet<[item: T, index: number]>;
}
let { items, children }: Props = $props();
</script>
<ul>
{#each items as item, index}
<li>{@render children(item, index)}</li>
{/each}
</ul>
<!-- Usage -->
<script lang="ts">
import List from './List.svelte';
interface User { id: number; name: string; }
let users: User[] = $state([{ id: 1, name: 'Alice' }]);
</script>
<List items={users}>
{#snippet children(user, index)}
<span>{index + 1}. {user.name}</span> <!-- Fully typed! -->
{/snippet}
</List>
```
### Multiple Type Parameters
```svelte
<script lang="ts" generics="K, V">
interface Props {
entries: [K, V][];
renderKey: import('svelte').Snippet<[key: K]>;
renderValue: import('svelte').Snippet<[value: V]>;
}
let { entries, renderKey, renderValue }: Props = $props();
</script>
<dl>
{#each entries as [key, value]}
<dt>{@render renderKey(key)}</dt>
<dd>{@render renderValue(value)}</dd>
{/each}
</dl>
```
### Constrained Generics
```svelte
<script lang="ts" generics="T extends { id: string | number }">
interface Props {
items: T[];
selected?: T;
onselect?: (item: T) => void;
children: import('svelte').Snippet<[item: T, isSelected: boolean]>;
}
let { items, selected, onselect, children }: Props = $props();
</script>
{#each items as item}
<div
class:selected={selected?.id === item.id}
onclick={() => onselect?.(item)}
>
{@render children(item, selected?.id === item.id)}
</div>
{/each}
```
### Generic with Default Type
```svelte
<script lang="ts" generics="T = Record<string, unknown>">
interface Props {
data: T[];
columns: (keyof T)[];
}
let { data, columns }: Props = $props();
</script>
```
### Generic Select Component
```svelte
<script lang="ts" generics="T">
interface Props {
options: T[];
value?: T;
getLabel: (option: T) => string;
getValue: (option: T) => string;
onchange?: (selected: T | undefined) => void;
placeholder?: string;
}
let {
options,
value = $bindable(),
getLabel,
getValue,
onchange,
placeholder = 'Select...'
}: Props = $props();
function handleChange(e: Event) {
const selectedValue = (e.target as HTMLSelectElement).value;
const selected = options.find(o => getValue(o) === selectedValue);
value = selected;
onchange?.(selected);
}
</script>
<select onchange={handleChange}>
<option value="">{placeholder}</option>
{#each options as option}
<option
value={getValue(option)}
selected={value && getValue(value) === getValue(option)}
>
{getLabel(option)}
</option>
{/each}
</select>
```
### Generic Async Component
```svelte
<script lang="ts" generics="T">
import type { Snippet } from 'svelte';
interface Props {
promise: Promise<T>;
loading?: Snippet;
error?: Snippet<[error: Error]>;
children: Snippet<[data: T]>;
}
let { promise, loading, error, children }: Props = $props();
</script>
{#await promise}
{#if loading}{@render loading()}{:else}<p>Loading...</p>{/if}
{:then data}
{@render children(data)}
{:catch err}
{#if error}{@render error(err)}{:else}<p>Error: {err.message}</p>{/if}
{/await}
```
SKILL.md
---
name: svelte5-best-practices
description: "Svelte 5 runes, snippets, SvelteKit patterns, and modern best practices for TypeScript and component development. Use when writing, reviewing, or refactoring Svelte 5 components and SvelteKit applications. Triggers on: Svelte components, runes ($state, $derived, $effect, $props, $bindable, $inspect), snippets ({#snippet}, {@render}), event handling, SvelteKit data loading, form actions, Svelte 4 to Svelte 5 migration, store to rune migration, slots to snippets migration, TypeScript props typing, generic components, SSR state isolation, performance optimization, or component testing."
license: MIT
metadata:
author: ejirocodes
version: '1.0.0'
---
# Svelte 5 Best Practices
## Quick Reference
| Topic | When to Use | Reference |
|-------|-------------|-----------|
| **Runes** | $state, $derived, $effect, $props, $bindable, $inspect | [runes.md](references/runes.md) |
| **Snippets** | Replacing slots, {#snippet}, {@render} | [snippets.md](references/snippets.md) |
| **Events** | onclick handlers, callback props, context API | [events.md](references/events.md) |
| **TypeScript** | Props typing, generic components | [typescript.md](references/typescript.md) |
| **Migration** | Svelte 4 to 5, stores to runes | [migration.md](references/migration.md) |
| **SvelteKit** | Load functions, form actions, SSR, page typing | [sveltekit.md](references/sveltekit.md) |
| **Performance** | Universal reactivity, avoiding over-reactivity, streaming | [performance.md](references/performance.md) |
## Essential Patterns
### Reactive State
```svelte
<script>
let count = $state(0); // Reactive state
let doubled = $derived(count * 2); // Computed value
</script>
```
### Component Props
```svelte
<script>
let { name, count = 0 } = $props();
let { value = $bindable() } = $props(); // Two-way binding
</script>
```
### Snippets (replacing slots)
```svelte
<script>
let { children, header } = $props();
</script>
{@render header?.()}
{@render children()}
```
### Event Handlers
```svelte
<!-- Svelte 5: use onclick, not on:click -->
<button onclick={() => count++}>Click</button>
```
### Callback Props (replacing createEventDispatcher)
```svelte
<script>
let { onclick } = $props();
</script>
<button onclick={() => onclick?.({ data })}>Click</button>
```
## Common Mistakes
1. **Using `let` without `$state`** - Variables are not reactive without `$state()`
2. **Using `$effect` for derived values** - Use `$derived` instead
3. **Using `on:click` syntax** - Use `onclick` in Svelte 5
4. **Using `createEventDispatcher`** - Use callback props instead
5. **Using `<slot>`** - Use snippets with `{@render}`
6. **Forgetting `$bindable()`** - Required for `bind:` to work
7. **Setting module-level state in SSR** - Causes cross-request leaks
8. **Sequential awaits in load functions** - Use `Promise.all` for parallel requests