Svelte is a compiler-based UI framework that transforms declarative component code into efficient imperative JavaScript that surgically updates the DOM
Svelte 5 introduces a powerful runes-based reactivity system that replaces the compiler-based reactivity of Svelte 4. The reactivity system consists of compile-time runes (special syntax processed by the compiler) and runtime APIs (reactive classes and functions).
Runes are special compile-time syntax available in .svelte files and Svelte components. They are not runtime imports but are transformed by the compiler into reactive code.
$stateDeclares deeply reactive state. When any property of the state changes, components and effects that read that property are automatically re-run.
declare function $state<T>(initial: T): T;
declare function $state<T>(): T | undefined;Usage:
<script>
let count = $state(0);
let user = $state({ name: 'Alice', age: 30 });
</script>
<button onclick={() => count++}>
Clicked {count} times
</button>
<button onclick={() => user.age++}>
{user.name} is {user.age} years old
</button>The state is deeply reactive, meaning nested properties are also tracked:
let obj = $state({ nested: { value: 0 } });
obj.nested.value++; // triggers reactivity$state.rawDeclares non-deeply-reactive state (shallow reactivity). Only the top-level value is reactive; nested properties are not tracked.
declare namespace $state {
/**
* Declares non-deeply-reactive state (shallow reactivity).
* @param initial - Initial value
* @returns The state value with shallow reactivity
*/
export function raw<T>(initial: T): T;
export function raw<T>(): T | undefined;
}Usage:
<script>
// Only changes to `items` itself are reactive, not changes to items' properties
let items = $state.raw([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]);
</script>
<button onclick={() => items = [...items, { id: 3, name: 'Item 3' }]}>
Add item (reactive)
</button>
<button onclick={() => items[0].name = 'Updated'}>
Update first item (not reactive)
</button>$state.snapshotTakes a static snapshot of proxied state. Returns a plain JavaScript object/array without any reactivity.
declare namespace $state {
/**
* Takes a static snapshot of proxied state.
* @param state - The reactive state to snapshot
* @returns A non-reactive copy of the state
*/
export function snapshot<T>(state: T): Snapshot<T>;
}Usage:
<script>
let state = $state({ count: 0, items: [1, 2, 3] });
function logSnapshot() {
// Get a plain, non-reactive copy
const snapshot = $state.snapshot(state);
console.log(snapshot); // { count: 0, items: [1, 2, 3] }
// snapshot is not reactive - changes won't trigger updates
snapshot.count++; // no effect on UI
}
</script>Use this when you need to:
$state.eagerGets the latest value during async suspension. Useful when working with async data and you need the most current value.
declare namespace $state {
/**
* Gets the latest value during async suspension.
* @param value - The value to eagerly evaluate
* @returns The eager value
* @since 5.42.0
*/
export function eager<T>(value: T): T;
}Usage:
<script>
let id = $state(1);
// Without eager, `id` might be stale during async operations
async function loadData() {
const currentId = $state.eager(id); // always gets latest value
const response = await fetch(`/api/data/${currentId}`);
return await response.json();
}
</script>$derivedDeclares derived/computed state from expressions. The value is automatically recalculated when its dependencies change.
/**
* Declares derived/computed state from expressions.
* @param expression - The expression to derive the value from
* @returns The derived value, automatically recalculated when dependencies change
*/
declare function $derived<T>(expression: T): T;Usage:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
let message = $derived(`Count is ${count}`);
</script>
<p>{count} × 2 = {doubled}</p>
<p>{message}</p>Derived values are read-only and only recalculate when accessed and their dependencies have changed.
$derived.byDeclares derived state with complex logic in a function. Use this when your derived value requires multiple statements or complex computation.
declare namespace $derived {
/**
* Declares derived state with complex logic in a function.
* @param fn - Function that computes the derived value
* @returns The derived value
*/
export function by<T>(fn: () => T): T;
}Usage:
<script>
let numbers = $state([1, 2, 3, 4, 5]);
let stats = $derived.by(() => {
const sum = numbers.reduce((a, b) => a + b, 0);
const avg = sum / numbers.length;
const max = Math.max(...numbers);
return { sum, avg, max };
});
</script>
<p>Sum: {stats.sum}</p>
<p>Average: {stats.avg}</p>
<p>Max: {stats.max}</p>$effectRuns side effects after DOM updates. Effects automatically track their dependencies and re-run when those dependencies change.
/**
* Runs side effects after DOM updates.
* @param fn - The effect function. Can return a cleanup function.
*/
declare function $effect(fn: () => void | (() => void)): void;Usage:
<script>
let count = $state(0);
$effect(() => {
// Runs after DOM updates whenever `count` changes
console.log(`Count is now ${count}`);
// Optional: return cleanup function
return () => {
console.log('Cleaning up previous effect');
};
});
</script>
<button onclick={() => count++}>Increment</button>Best Practices:
$derived instead)$effect.preRuns side effects before DOM updates. Useful when you need to measure DOM properties before they change.
declare namespace $effect {
/**
* Runs side effects before DOM updates.
* @param fn - The effect function. Can return a cleanup function.
*/
export function pre(fn: () => void | (() => void)): void;
}Usage:
<script>
let items = $state([1, 2, 3]);
let listElement;
$effect.pre(() => {
// Runs before the DOM updates
if (listElement) {
const oldHeight = listElement.offsetHeight;
console.log('Height before update:', oldHeight);
}
});
</script>
<ul bind:this={listElement}>
{#each items as item}
<li>{item}</li>
{/each}
</ul>$effect.rootCreates a manually-controlled effect root that persists until explicitly destroyed. Returns a cleanup function.
declare namespace $effect {
/**
* Creates manually-controlled effect root that persists until explicitly destroyed.
* @param fn - The effect function
* @returns A cleanup function to destroy the effect
*/
export function root(fn: () => void | (() => void)): () => void;
}Usage:
<script>
let count = $state(0);
let cleanup;
function startTracking() {
cleanup = $effect.root(() => {
$effect(() => {
console.log('Count changed:', count);
});
});
}
function stopTracking() {
cleanup?.(); // Destroy the effect
}
</script>
<button onclick={startTracking}>Start tracking</button>
<button onclick={stopTracking}>Stop tracking</button>
<button onclick={() => count++}>Increment</button>$effect.trackingReturns true if code is running in a tracking context (inside an effect or derived).
declare namespace $effect {
/**
* Returns true if code is running in a tracking context.
* @returns Whether the code is currently tracking reactive dependencies
*/
export function tracking(): boolean;
}Usage:
<script>
function doSomething() {
if ($effect.tracking()) {
console.log('Called from reactive context');
} else {
console.log('Called from non-reactive context');
}
}
$effect(() => {
doSomething(); // logs "Called from reactive context"
});
doSomething(); // logs "Called from non-reactive context"
</script>$effect.pendingReturns the number of pending promises in current effects. Useful for showing loading states.
declare namespace $effect {
/**
* Returns the number of pending promises in current effects.
* @returns The count of pending async operations
* @since 5.42.0
*/
export function pending(): number;
}Usage:
<script>
let id = $state(1);
$effect(() => {
const pending = $effect.pending();
console.log(`${pending} operations pending`);
});
async function loadData() {
await fetch(`/api/data/${id}`);
}
</script>$propsDeclares component props with destructuring. Props are read-only by default.
/**
* Declares component props with destructuring.
* @returns The props object
*/
declare function $props(): any;Usage:
<!-- MyComponent.svelte -->
<script>
let {
title,
count = 0, // with default value
items = [] // with default array
} = $props();
</script>
<h1>{title}</h1>
<p>Count: {count}</p>
<ul>
{#each items as item}
<li>{item}</li>
{/each}
</ul>Type-safe props with TypeScript:
<script lang="ts">
interface Props {
title: string;
count?: number;
items?: string[];
}
let { title, count = 0, items = [] }: Props = $props();
</script>$props.idGenerates a unique ID for the component instance. Useful for accessibility attributes.
declare namespace $props {
/**
* Generates a unique ID for the component instance.
* @returns A unique string identifier
* @since 5.20.0
*/
export function id(): string;
}Usage:
<script>
let { label } = $props();
const inputId = $props.id();
</script>
<label for={inputId}>{label}</label>
<input id={inputId} type="text" />$bindableDeclares a prop as bindable (two-way binding). Allows parent components to use bind: directive.
/**
* Declares a prop as bindable (two-way binding).
* @param fallback - Optional fallback value if prop is not provided
* @returns The bindable prop value
*/
declare function $bindable<T>(fallback?: T): T;Usage:
<!-- Counter.svelte -->
<script>
let { count = $bindable(0) } = $props();
</script>
<button onclick={() => count++}>
Count: {count}
</button><!-- Parent.svelte -->
<script>
import Counter from './Counter.svelte';
let value = $state(0);
</script>
<Counter bind:count={value} />
<p>Parent knows count is: {value}</p>$inspectLogs reactive values whenever they change. Only works in development mode.
/**
* Logs reactive values whenever they change (dev mode only).
* @param values - Values to inspect
*/
declare function $inspect<T extends any[]>(
...values: T
): void;Usage:
<script>
let count = $state(0);
let user = $state({ name: 'Alice' });
// Logs to console whenever count or user changes
$inspect(count, user);
</script>
<button onclick={() => count++}>Increment</button>
<input bind:value={user.name} />$inspect.traceTracks and logs effect dependencies for debugging. Shows what state changes trigger effects.
declare namespace $inspect {
/**
* Tracks and logs effect dependencies for debugging.
* @param name - Optional name for the trace
*/
export function trace(name?: string): void;
}Usage:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
$effect(() => {
$inspect.trace('count-effect');
console.log(count, doubled);
});
</script>$hostGets reference to custom element host when component is compiled as a custom element.
/**
* Gets reference to custom element host when component is compiled as custom element.
* @returns The host HTMLElement
*/
declare function $host<El extends HTMLElement = HTMLElement>(): El;Usage:
<svelte:options customElement="my-counter" />
<script>
let count = $state(0);
const host = $host();
$effect(() => {
// Dispatch custom event on the host element
host.dispatchEvent(new CustomEvent('countchange', {
detail: { count }
}));
});
</script>
<button onclick={() => count++}>
Count: {count}
</button>The svelte/reactivity module provides reactive wrappers for built-in JavaScript objects. These classes maintain standard API compatibility while adding automatic reactivity.
SvelteMap<K, V>A reactive version of the built-in Map object.
/**
* A reactive version of the built-in Map object.
* Reading contents of the map (by iterating, or by reading `map.size` or
* calling `map.get(...)` or `map.has(...)`) in an effect or derived will
* cause it to be re-evaluated as necessary when the map is updated.
*
* Note that values in a reactive map are not made deeply reactive.
*/
export class SvelteMap<K, V> extends Map<K, V> {
constructor(value?: Iterable<readonly [K, V]> | null | undefined);
set(key: K, value: V): this;
}Usage:
<script>
import { SvelteMap } from 'svelte/reactivity';
let board = new SvelteMap();
let player = $state('x');
function makeMove(position) {
board.set(position, player);
player = player === 'x' ? 'o' : 'x';
}
</script>
<div class="board">
{#each Array(9) as _, i}
<button
disabled={board.has(i)}
onclick={() => makeMove(i)}
>
{board.get(i) ?? ''}
</button>
{/each}
</div>
<p>Size: {board.size}</p>All standard Map methods are available:
set(key, value) - Add or update entry (triggers reactivity)get(key) - Get value (tracked)has(key) - Check existence (tracked)delete(key) - Remove entry (triggers reactivity)clear() - Remove all entries (triggers reactivity)size - Number of entries (tracked)keys(), values(), entries() - Iterators (tracked)forEach() - Iterate over entries (tracked)SvelteSet<T>A reactive version of the built-in Set object.
/**
* A reactive version of the built-in Set object.
* Reading contents of the set (by iterating, or by reading `set.size` or
* calling `set.has(...)`) in an effect or derived will cause it to be
* re-evaluated as necessary when the set is updated.
*
* Note that values in a reactive set are not made deeply reactive.
*/
export class SvelteSet<T> extends Set<T> {
constructor(value?: Iterable<T> | null | undefined);
add(value: T): this;
}Usage:
<script>
import { SvelteSet } from 'svelte/reactivity';
let selected = new SvelteSet();
function toggle(item) {
if (selected.has(item)) {
selected.delete(item);
} else {
selected.add(item);
}
}
</script>
{#each ['🙈', '🙉', '🙊'] as emoji}
<button onclick={() => toggle(emoji)}>
{emoji} {selected.has(emoji) ? '✓' : ''}
</button>
{/each}
<p>Selected: {selected.size}</p>
<button onclick={() => selected.clear()}>Clear</button>All standard Set methods are available:
add(value) - Add item (triggers reactivity)has(value) - Check existence (tracked)delete(value) - Remove item (triggers reactivity)clear() - Remove all items (triggers reactivity)size - Number of items (tracked)keys(), values(), entries() - Iterators (tracked)forEach() - Iterate over items (tracked)SvelteDateA reactive version of the built-in Date object.
/**
* A reactive version of the built-in Date object.
* Calling any method that modifies the date will cause effects and derived
* values that read from it to re-run.
*/
export class SvelteDate extends Date {
constructor(...params: any[]);
}Usage:
<script>
import { SvelteDate } from 'svelte/reactivity';
const date = new SvelteDate();
$effect(() => {
const interval = setInterval(() => {
date.setTime(Date.now());
}, 1000);
return () => clearInterval(interval);
});
const formatter = new Intl.DateTimeFormat('en', {
hour12: true,
hour: 'numeric',
minute: '2-digit',
second: '2-digit'
});
</script>
<p>The time is {formatter.format(date)}</p>All standard Date methods are available. Methods that mutate the date trigger reactivity:
setTime(), setFullYear(), setMonth(), setDate()setHours(), setMinutes(), setSeconds(), setMilliseconds()SvelteURLA reactive version of the built-in URL object.
/**
* A reactive version of the built-in URL object.
* Reading properties of the URL (such as `url.href` or `url.pathname`) in
* an effect or derived will cause it to be re-evaluated as necessary when
* the URL changes.
*
* The `searchParams` property is an instance of SvelteURLSearchParams.
*/
export class SvelteURL extends URL {
get searchParams(): SvelteURLSearchParams;
}Usage:
<script>
import { SvelteURL } from 'svelte/reactivity';
const url = new SvelteURL('https://example.com/path');
</script>
<!-- Changes to these... -->
<input bind:value={url.protocol} />
<input bind:value={url.hostname} />
<input bind:value={url.pathname} />
<hr />
<!-- will update `href` and vice versa -->
<input bind:value={url.href} size="65" />Reactive URL properties:
href, origin, protocol, username, passwordhost, hostname, portpathname, search, hashsearchParams (returns SvelteURLSearchParams)SvelteURLSearchParamsA reactive version of the built-in URLSearchParams object.
/**
* A reactive version of the built-in URLSearchParams object.
* Reading its contents (by iterating, or by calling `params.get(...)` or
* `params.getAll(...)`) in an effect or derived will cause it to be
* re-evaluated as necessary when the params are updated.
*/
export class SvelteURLSearchParams extends URLSearchParams {
constructor(init?: string | URLSearchParams | Record<string, string> | Iterable<[string, string]> | Array<[string, string]>);
}Usage:
<script>
import { SvelteURLSearchParams } from 'svelte/reactivity';
const params = new SvelteURLSearchParams('message=hello&name=world');
let key = $state('');
let value = $state('');
</script>
<input bind:value={key} placeholder="Key" />
<input bind:value={value} placeholder="Value" />
<button onclick={() => params.append(key, value)}>Append</button>
<p>?{params.toString()}</p>
{#each params as [key, value]}
<p>{key}: {value}</p>
{/each}All standard URLSearchParams methods are available:
append(key, value) - Add parameter (triggers reactivity)set(key, value) - Set parameter (triggers reactivity)delete(key) - Remove parameter (triggers reactivity)get(key) - Get value (tracked)getAll(key) - Get all values (tracked)has(key) - Check existence (tracked)toString() - Serialize to string (tracked)MediaQueryCreates a media query and provides a current property that reflects whether or not it matches.
/**
* Creates a media query and provides a `current` property that reflects
* whether or not it matches.
*
* Use it carefully — during server-side rendering, there is no way to know
* what the correct value should be, potentially causing content to change
* upon hydration. If you can use the media query in CSS to achieve the same
* effect, do that.
*
* @since 5.7.0
*/
export class MediaQuery {
/**
* @param query - A media query string
* @param fallback - Fallback value for the server
*/
constructor(query: string, fallback?: boolean | undefined);
/**
* Whether the media query currently matches
*/
get current(): boolean;
}Usage:
<script>
import { MediaQuery } from 'svelte/reactivity';
const large = new MediaQuery('(min-width: 800px)');
const dark = new MediaQuery('(prefers-color-scheme: dark)');
const reduced = new MediaQuery('(prefers-reduced-motion: reduce)');
</script>
<h1>{large.current ? 'Large screen' : 'Small screen'}</h1>
<p>Theme: {dark.current ? 'Dark' : 'Light'}</p>
<p>Motion: {reduced.current ? 'Reduced' : 'Normal'}</p>SSR Considerations:
// Provide fallback for server-side rendering
const large = new MediaQuery('(min-width: 800px)', false);createSubscriber()Creates a subscriber function that integrates external event-based systems with Svelte's reactivity.
/**
* Returns a `subscribe` function that integrates external event-based systems
* with Svelte's reactivity. It's particularly useful for integrating with web
* APIs like `MediaQuery`, `IntersectionObserver`, or `WebSocket`.
*
* If `subscribe` is called inside an effect (including indirectly, for example
* inside a getter), the `start` callback will be called with an `update`
* function. Whenever `update` is called, the effect re-runs.
*
* If `start` returns a cleanup function, it will be called when the effect
* is destroyed.
*
* If `subscribe` is called in multiple effects, `start` will only be called
* once as long as the effects are active, and the returned teardown function
* will only be called when all effects are destroyed.
*
* @param start - Function called when first subscriber subscribes
* @returns A subscribe function to use in reactive contexts
* @since 5.7.0
*/
export function createSubscriber(
start: (update: () => void) => (() => void) | void
): () => void;Usage - Custom MediaQuery implementation:
import { createSubscriber } from 'svelte/reactivity';
import { on } from 'svelte/events';
export class CustomMediaQuery {
#query;
#subscribe;
constructor(query) {
this.#query = window.matchMedia(`(${query})`);
this.#subscribe = createSubscriber((update) => {
// When the 'change' event occurs, re-run any effects that read this.current
const off = on(this.#query, 'change', update);
// Stop listening when all the effects are destroyed
return () => off();
});
}
get current() {
// This makes the getter reactive, if read in an effect
this.#subscribe();
// Return the current state of the query, whether or not we're in an effect
return this.#query.matches;
}
}Usage - IntersectionObserver:
import { createSubscriber } from 'svelte/reactivity';
export class IntersectionState {
#element;
#subscribe;
#isIntersecting = false;
constructor(element, options) {
this.#element = element;
this.#subscribe = createSubscriber((update) => {
const observer = new IntersectionObserver(([entry]) => {
this.#isIntersecting = entry.isIntersecting;
update();
}, options);
observer.observe(this.#element);
return () => observer.disconnect();
});
}
get isIntersecting() {
this.#subscribe();
return this.#isIntersecting;
}
}Usage - WebSocket:
import { createSubscriber } from 'svelte/reactivity';
export class ReactiveWebSocket {
#ws;
#subscribe;
#data = null;
constructor(url) {
this.#ws = new WebSocket(url);
this.#subscribe = createSubscriber((update) => {
const handleMessage = (event) => {
this.#data = event.data;
update();
};
this.#ws.addEventListener('message', handleMessage);
return () => {
this.#ws.removeEventListener('message', handleMessage);
this.#ws.close();
};
});
}
get data() {
this.#subscribe();
return this.#data;
}
send(message) {
this.#ws.send(message);
}
}The svelte/reactivity/window module provides reactive properties that automatically update based on window state changes.
All exports have a .current getter that returns the current value. Reading .current in an effect or derived makes it reactive.
/**
* `scrollX.current` is a reactive view of `window.scrollX`.
* On the server it is `undefined`.
* @since 5.11.0
*/
export const scrollX: ReactiveValue<number | undefined>;
/**
* `scrollY.current` is a reactive view of `window.scrollY`.
* On the server it is `undefined`.
* @since 5.11.0
*/
export const scrollY: ReactiveValue<number | undefined>;
/**
* `innerWidth.current` is a reactive view of `window.innerWidth`.
* On the server it is `undefined`.
* @since 5.11.0
*/
export const innerWidth: ReactiveValue<number | undefined>;
/**
* `innerHeight.current` is a reactive view of `window.innerHeight`.
* On the server it is `undefined`.
* @since 5.11.0
*/
export const innerHeight: ReactiveValue<number | undefined>;
/**
* `outerWidth.current` is a reactive view of `window.outerWidth`.
* On the server it is `undefined`.
* @since 5.11.0
*/
export const outerWidth: ReactiveValue<number | undefined>;
/**
* `outerHeight.current` is a reactive view of `window.outerHeight`.
* On the server it is `undefined`.
* @since 5.11.0
*/
export const outerHeight: ReactiveValue<number | undefined>;Usage:
<script>
import {
scrollX,
scrollY,
innerWidth,
innerHeight
} from 'svelte/reactivity/window';
// Automatically reactive - updates when window size/scroll changes
let isScrolled = $derived(scrollY.current > 100);
let isMobile = $derived(innerWidth.current < 768);
</script>
<div class:scrolled={isScrolled}>
<p>Window: {innerWidth.current} × {innerHeight.current}</p>
<p>Scroll: {scrollX.current}, {scrollY.current}</p>
<p>Device: {isMobile ? 'Mobile' : 'Desktop'}</p>
</div>/**
* `screenLeft.current` is a reactive view of `window.screenLeft`.
* It is updated inside a `requestAnimationFrame` callback.
* On the server it is `undefined`.
* @since 5.11.0
*/
export const screenLeft: ReactiveValue<number | undefined>;
/**
* `screenTop.current` is a reactive view of `window.screenTop`.
* It is updated inside a `requestAnimationFrame` callback.
* On the server it is `undefined`.
* @since 5.11.0
*/
export const screenTop: ReactiveValue<number | undefined>;Usage:
<script>
import { screenLeft, screenTop } from 'svelte/reactivity/window';
</script>
<p>Window position: {screenLeft.current}, {screenTop.current}</p>/**
* `online.current` is a reactive view of `navigator.onLine`.
* On the server it is `undefined`.
* @since 5.11.0
*/
export const online: ReactiveValue<boolean | undefined>;Usage:
<script>
import { online } from 'svelte/reactivity/window';
</script>
{#if online.current === false}
<div class="offline-banner">
You are currently offline
</div>
{/if}/**
* `devicePixelRatio.current` is a reactive view of `window.devicePixelRatio`.
* On the server it is `undefined`.
*
* Note that behaviour differs between browsers — on Chrome it will respond
* to the current zoom level, on Firefox and Safari it won't.
*
* @since 5.11.0
*/
export const devicePixelRatio: {
get current(): number | undefined;
};Usage:
<script>
import { devicePixelRatio } from 'svelte/reactivity/window';
let isHighDPI = $derived(devicePixelRatio.current >= 2);
</script>
<img
src={isHighDPI ? 'image@2x.png' : 'image.png'}
alt="Responsive to pixel density"
/>Runtime functions from the main svelte module that control reactivity behavior.
untrack()Runs a function without creating reactive dependencies. Useful when you want to read state without triggering re-runs.
/**
* Runs function without creating reactive dependencies.
* @param fn - Function to run without tracking
* @returns The return value of the function
*/
export function untrack<T>(fn: () => T): T;Usage:
<script>
import { untrack } from 'svelte';
let count = $state(0);
let name = $state('Alice');
$effect(() => {
// This effect only re-runs when `count` changes
console.log('Count:', count);
// Reading `name` doesn't create a dependency
const currentName = untrack(() => name);
console.log('Name (untracked):', currentName);
});
</script>
<button onclick={() => count++}>Increment</button>
<input bind:value={name} />Common use cases:
fork()Creates a speculative state fork in which state changes are evaluated but not applied to the DOM. Useful for preloading data.
/**
* Creates a 'fork', in which state changes are evaluated but not applied
* to the DOM. This is useful for speculatively loading data (for example)
* when you suspect that the user is about to take some action.
*
* The `fn` parameter is a synchronous function that modifies some state.
* The state changes will be reverted after the fork is initialised, then
* reapplied if and when the fork is eventually committed.
*
* When it becomes clear that a fork will not be committed (e.g. because
* the user navigated elsewhere), it must be discarded to avoid leaking memory.
*
* @param fn - Synchronous function that modifies state
* @returns Fork object with commit() and discard() methods
* @since 5.42.0
*/
export function fork(fn: () => void): Fork;
export interface Fork {
/**
* Commit the fork. The promise will resolve once the state change
* has been applied to the DOM.
*/
commit(): Promise<void>;
/**
* Discard the fork
*/
discard(): void;
}Usage - Preloading data on hover:
<script>
import { fork } from 'svelte';
let data = $state(null);
let currentFork = null;
async function loadData(id) {
const response = await fetch(`/api/data/${id}`);
data = await response.json();
}
function preload(id) {
// Create a fork to speculatively load data
currentFork = fork(() => {
loadData(id);
});
}
function navigate(id) {
if (currentFork) {
// Commit the speculative changes
currentFork.commit();
currentFork = null;
} else {
loadData(id);
}
}
function cancelPreload() {
if (currentFork) {
currentFork.discard();
currentFork = null;
}
}
</script>
<a
href="/item/123"
onmouseenter={() => preload(123)}
onmouseleave={cancelPreload}
onclick={(e) => {
e.preventDefault();
navigate(123);
}}
>
View Item
</a>Key points:
fork() must be synchronouscommit() to apply the changes permanentlydiscard() to abandon the fork and free memorygetAbortSignal()Returns an AbortSignal that aborts when the current derived or effect re-runs or is destroyed.
/**
* Returns an AbortSignal that aborts when the current derived or effect
* re-runs or is destroyed.
*
* Must be called while a derived or effect is running.
*/
export function getAbortSignal(): AbortSignal;Usage - Canceling fetch requests:
<script>
import { getAbortSignal } from 'svelte';
let id = $state(1);
async function getData(id) {
const response = await fetch(`/api/items/${id}`, {
signal: getAbortSignal()
});
return await response.json();
}
// Automatically cancels previous request when id changes
const data = $derived(await getData(id));
</script>
<input type="number" bind:value={id} />
{#if data}
<pre>{JSON.stringify(data, null, 2)}</pre>
{/if}Usage - Aborting long-running operations:
<script>
import { getAbortSignal } from 'svelte';
let query = $state('');
$effect(() => {
const signal = getAbortSignal();
const timer = setTimeout(() => {
if (!signal.aborted) {
performSearch(query);
}
}, 300);
signal.addEventListener('abort', () => {
clearTimeout(timer);
});
});
</script>Key points:
$effect or $derivedAbortSignal (fetch, DOM APIs, etc.)Use $state for component-local state:
<script>
let count = $state(0);
</script>Use $derived for computed values:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>Use $state.raw for large data structures:
<script>
// Only top-level changes are tracked
let bigList = $state.raw([/* thousands of items */]);
</script>Prefer $derived over $effect for deriving state:
<!-- Good -->
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
<!-- Bad -->
<script>
let count = $state(0);
let doubled = $state(0);
$effect(() => {
doubled = count * 2; // Don't do this
});
</script>Always clean up in effects:
<script>
$effect(() => {
const interval = setInterval(() => {
// ...
}, 1000);
return () => clearInterval(interval);
});
</script>Use untrack() to avoid unwanted dependencies:
<script>
import { untrack } from 'svelte';
let x = $state(0);
let y = $state(0);
$effect(() => {
console.log('x changed:', x);
// Reading y doesn't create dependency
const currentY = untrack(() => y);
});
</script>Use reactive classes for data structures:
<script>
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
let users = new SvelteMap();
let selectedIds = new SvelteSet();
</script>Remember they use shallow reactivity:
<script>
import { SvelteMap } from 'svelte/reactivity';
let map = new SvelteMap();
map.set('user', { name: 'Alice' });
// This triggers reactivity
map.set('user', { name: 'Bob' });
// This does NOT trigger reactivity
map.get('user').name = 'Charlie';
</script>Use $state.raw for performance-critical data:
let hugeArray = $state.raw(new Array(10000));Debounce expensive derived computations:
<script>
let input = $state('');
let debounced = $state('');
$effect(() => {
const timer = setTimeout(() => {
debounced = input;
}, 300);
return () => clearTimeout(timer);
});
let expensiveResult = $derived(computeExpensive(debounced));
</script>Use getAbortSignal() to cancel stale requests:
const data = $derived(await fetch('/api', {
signal: getAbortSignal()
}));<!-- Svelte 4 -->
<script>
let count = 0;
</script>
<!-- Svelte 5 -->
<script>
let count = $state(0);
</script><!-- Svelte 4 -->
<script>
let count = 0;
$: doubled = count * 2;
</script>
<!-- Svelte 5 -->
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script><!-- Svelte 4 -->
<script>
let count = 0;
$: {
console.log(count);
}
</script>
<!-- Svelte 5 -->
<script>
let count = $state(0);
$effect(() => {
console.log(count);
});
</script><!-- Svelte 4 -->
<script>
export let title;
export let count = 0;
</script>
<!-- Svelte 5 -->
<script>
let { title, count = 0 } = $props();
</script><!-- Svelte 4 -->
<!-- Parent.svelte -->
<Child bind:value />
<!-- Child.svelte -->
<script>
export let value;
</script>
<!-- Svelte 5 -->
<!-- Parent.svelte -->
<Child bind:value />
<!-- Child.svelte -->
<script>
let { value = $bindable() } = $props();
</script>Install with Tessl CLI
npx tessl i tessl/npm-svelte