Svelte Cheatsheet - Reactivity, Stores & Slots
For frontend developers who want less boilerplate and a minimal runtime. Svelte compiles reactivity directly into native DOM updates at build time, so the runtime is tiny and there is no virtual-DOM mental model; but it lives on assignment-driven updates, $: recomputation, and Store subscriptions that take getting used to. By the end you can declare reactive state with let and $:, communicate between components via events and dispatch, share cross-component state with Store, and hook into lifecycle and transitions.
Typical Use Case
For developers moving over from React or Vue, or working on projects where component weight is a real concern. The sheet follows the three main lines of Svelte: template block syntax handles structure, assignment-driven reactivity handles data, and actions plus transitions handle interaction. Above that sit lifecycle hooks for side effects, tick for waiting on DOM updates, and stores for pulling state out of components so it can be shared and torn down cleanly.
Template Syntax 6
{ expression }{@html rawHtmlString}{#if condition}...{:else if}...{:else}...{/if}{#each items as item, index}...{/each}{#await promise}...{:then value}...{:catch error}...{/await}{#key expression}...{/key}Reactive Declarations 5
let count = 0;$: doubled = count * 2;$: { console.log(count); }arr = [...arr, newItem];obj = { ...obj, key: value };Components & Props 7
export let name;export let name = "default";$$props$$restProps<slot /><slot name="header" /><slot let:item={item} />Events & Bindings 7
<button on:click={handler}><form on:submit|preventDefault={handler}>import { createEventDispatcher } from "svelte"; const dispatch = createEventDispatcher(); dispatch("event", data);<input bind:value={name}><input bind:this={inputEl}><input type="checkbox" bind:group={selected}><input type="radio" bind:group={choice}>Lifecycle 6
import { onMount } from "svelte"; onMount(() => { ... });import { beforeUpdate } from "svelte"; beforeUpdate(() => { ... });import { afterUpdate } from "svelte"; afterUpdate(() => { ... });import { onDestroy } from "svelte"; onDestroy(() => { ... });import { tick } from "svelte"; await tick();import { untrack } from "svelte"; untrack(() => { ... });Store 7
import { writable } from "svelte/store"; export const count = writable(0);import { readable } from "svelte/store"; export const time = readable(new Date(), (set) => { ... });import { derived } from "svelte/store"; export const doubled = derived(count, ($c) => $c * 2);$countcount.set(10);count.update(n => n + 1);import { get } from "svelte/store"; const val = get(count);Actions & Transitions 7
use:action={{ param }}transition:fadetransition:slidetransition:scalein:fly={{ x: 200, duration: 500 }}out:fly={{ x: -200 }}animate:flipAdvanced Features 5
import { setContext, getContext } from "svelte"; setContext("key", value); const val = getContext("key");import { hasContext } from "svelte"; hasContext("key");<script context="module">...</script>export const greet = (name) => `Hello ${name}`;svelte:optionsParameter matrix
| Parameter | Effect | Example |
|---|---|---|
{ expression } | Insert a JavaScript expression into the template | <h1>{user.name}</h1> |
{@html raw} | Render a raw HTML string; carries XSS risk | {@html post.bodyHtml} |
{#if}...{:else if}...{:else} | Conditional rendering branches | {#if loading}<Spinner />{:else}<List />{/if} |
{#each items as item, i} | Iterate an array to render, with the second item as the index | {#each todos as todo, i}{i}. {todo.text}{/each} |
{#await promise} | Render an async result across the pending/then/catch three states | {#await loadUser()}<Spinner />{:then u}<Profile {u} />{/await} |
{#key expression} | Destroy and rebuild the inner content when the expression changes | {#key activeTab}<Panel {tab} />{/key} |
$: doubled = count * 2 | Reactive declaration, recomputed automatically when dependencies change | $: total = price * qty |
arr = [...arr, x] | Array updates require assigning a new reference to trigger an update | items = [...items, newItem] |
obj = { ...obj, k: v } | Object updates likewise require assigning a whole new reference | state = { ...state, open: true } |
export let name | Declare a prop the component receives | export let name; |
$$restProps | Read the remaining props not explicitly declared | <input {...$$restProps} /> |
<slot name="header" /> | Named slot for a parent component to fill a given area | <slot name="header" /> |
<slot let:item={item} /> | Scoped slot that passes child data back to the parent | <slot let:item={item}>{item.label}</slot> |
on:click|preventDefault | Bind a DOM event and use event modifiers | <form on:submit|preventDefault={save}> |
createEventDispatcher() | Dispatch a custom event from child to parent | dispatch('change', data) |
bind:value / bind:this | Two-way bind an input value / bind a DOM element reference | <input bind:value={name} bind:this={inputEl} /> |
bind:group | Bind a checkbox group to an array, a radio group to a variable | <input type="checkbox" bind:group={selected} value={id} /> |
onMount / onDestroy | Run a request after mount / clean up timers and subscriptions before destroy | onDestroy(() => clearInterval(timer)) |
await tick() | Wait for the DOM update to finish before reading element state | await tick(); el.focus(); |
untrack(() => ...) | Temporarily disable dependency tracking within a reactive declaration | untrack(() => log(count)) |
writable / readable / derived | Create writable, readable, and derived Stores | const doubled = derived(count, (n) => n * 2) |
get(store) | Read the current value of a Store synchronously in a non-component context | const val = get(count) |
use:action | Custom action function to manipulate a DOM element | use:tooltip |
transition:fade / slide / scale | Play a transition animation when the element appears and disappears | transition:slide |
in:fly={{ x: 200 }} | Enter animation (fly in); out: is the matching leave animation | in:fly={{ x: 200, duration: 500 }} |
animate:flip | Tween animation (FLIP) when list elements change position | animate:flip={{ duration: 200 }} |
setContext / getContext | Cross-level component communication; hasContext checks whether one exists | setContext('theme', 'dark') |
svelte:options | Compiler options, such as immutable disabling deep proxying | <svelte:options immutable={true} /> |
context='module' | Module-level script, executed once per component instance | <script context="module"> |
Common pitfalls
SymptomYou pushed an array element or set an object property, but the UI does not update
CauseReactivity in Svelte is based on assignment tracking; in-place mutations like push and obj.x = v do not trigger a reassignment
FixUse a new-reference style instead: arr = [...arr, newItem], obj = { ...obj, key: value }
SymptomAfter async data returns, the DOM has not updated yet but you read input.value and get the old value
CauseThe assignment only enqueues an update; DOM rendering happens on the next microtask
Fixawait tick() after the assignment, and read the element state only once the DOM update completes
SymptomThe component keeps hitting the API after being destroyed, flooding the console with requests or setState warnings
CauseA request started in onMount is not cancelled when the component is destroyed
FixStore the AbortController or cancel handle in a variable, and cancel the request and clear timers in onDestroy
SymptomA reactive declaration should read a value only once, yet unrelated variable changes trigger recomputation
CauseA $: block automatically collects every dependency read inside it, including reads from non-reactive sources
FixPut the pure read inside untrack(() => ...) so it does not participate in dependency tracking
SymptomAfter toggling a list item state, animations glitch or the previous DOM is reused
CauseThe list lacks key semantics, so Svelte reuses elements by position
FixWrap the content that needs rebuilding in {#key expression}, or adjust the source structure of the {#each}
SymptomScripts execute after {@html} renders user-submitted content
Cause{@html} parses the string directly as HTML output without escaping
FixSanitize user input first, or switch to a plain { expression } so Svelte escapes it automatically
SymptomReading a Store outside the component or in an event callback yields undefined
CauseThe $store auto-subscribe syntax only works at the top level of a component; outside a component there is no subscriber
FixUse get(store) to read the value synchronously in non-component contexts, or subscribe manually with store.subscribe()
Troubleshooting
1Confirm whether a state actually triggered a re-render
$: console.log(count);Print the dependency variable inside a reactive statement block; new console output means the assignment was tracked.
2Confirm whether array/object updates went through a new reference
arr = [...arr, newItem];Replace the in-place push with a whole assignment; if the UI updates right after, the culprit was that line.
3Focus or measure an element right after the DOM updates
await tick();After tick() the DOM is up to date, suitable for focus(), getBoundingClientRect(), and the like.
4Exclude reads that should not be tracked from the reactive declaration
untrack(() => render(count));If the log shows render being called repeatedly due to an unrelated variable, wrap that read in untrack.
5Force a section of content to be rebuilt rather than reused
{#key activeId}<Panel data={activeId} />{/key}When content switches to another data source but still shows the old content, declare a rebuild boundary explicitly with {#key}.
6Read the current value of a Store outside a component
const val = get(count);get() reads the current value synchronously, suited to event callbacks, timers, module top level, and other places without component context.
7Dispatch an event from a child and receive it in the parent
dispatch('change', data);The parent receives it with on:change={handler}, and detail holds the data passed to dispatch.
Command Examples
Render an indexed list with each and the index
{#each items as item, index}
<li>{index + 1}. {item}</li>
{/each}index is the second binding of {#each} and can be used directly in markup. Supply a stable key with the (item.id) form; keying on the object itself rebuilds the whole block whenever the data is recreated, which is slower, not faster.
The await block covers loading, success, and failure states
{#await promise}
<p>Loading…</p>
{:then user}
<p>{user.name}</p>
{:catch error}
<p>{error.message}</p>
{/await}If you write only {#await promise}…{/await} with no :then and :catch, a rejected promise shows no feedback at all — the UI just keeps looking like it is loading. Writing all three branches is the cheapest way to avoid silent failure.
Use get() outside components instead of the store-value sugar
export const count = writable(0);
count.update((n) => n + 1);
// Reading outside a component:
import { get } from "svelte/store";
const val = get(count);$count is compile-time sugar and only works inside a component or .svelte file. Module-level event handlers and utility functions must use get(). Writing $count there is a compile error whose stack often points at a distant, seemingly unrelated file.
Fetch on mount, clean up on destroy
import { onMount, onDestroy } from "svelte";
let timer;
onMount(() => {
timer = setInterval(refresh, 5000);
});
onDestroy(() => {
clearInterval(timer);
});onMount runs on every mount, and conditional rendering unmounts the component while the timer, subscription, or socket keeps running. Teardown belongs in onDestroy; otherwise toggling the component in and out of the page stacks up duplicate timers.
derived computes from an existing store instead of duplicating state
export const doubled = derived(count, (value) => value * 2);A common mistake is copying a derived value into a component-local variable, which creates two sources of truth. derived makes it a store: it recomputes automatically and every subscriber sees the same value.
Common Pitfalls
- {@html rawHtmlString} injects raw markup, so anything from a user or an API is an open XSS hole. Use a plain interpolation unless you truly know the content is trusted.
- Assigning to a child component's export let prop silently mutates the parent's state. Props are a one-way source; use events or bind to change state across a boundary.
- A $: reactive statement only tracks variables that appear directly in it. Hide the value inside an object or pass it through a function and the dependency is never registered, so the UI stops updating.
- A request started in onMount with no check for an unmounted component throws or leaks when the response lands. Guard it with a flag in the returned cleanup function.
- {#key} destroys and rebuilds everything inside it every time the expression changes. Placed outside an #each, or bound to something that changes often, it rebuilds whole DOM subtrees repeatedly.
- Outside SSR-safe paths, code touching window or document crashes on the server. Guard the type check, or access them only after mount.
Tips
- Svelte is a compiled framework with no runtime, producing minimal code
- Reactivity is triggered by assignment; arrays and objects need new assignments
- $: reactive declarations track any dependency changes
- Store manages state outside components, $store syntax auto-subscribes
- Svelte 5 introduces Runes ($state/$derived/$effect) new reactivity system
FAQ
Why does the view not update when I push an item onto an array?
Svelte reactivity is assignment-driven, so arrays and objects must be replaced with a new reference to trigger an update. Use arr = [...arr, newItem] to append and obj = { ...obj, key: value } to change a field, never arr.push(x) or obj.k = v, which mutate in place. With Svelte 5 you can wrap the value in a $state rune for an explicitly reactive container, which follows the same replace-the-reference rule. Change those two spots and the template re-renders.
How does a $: reactive statement differ from a plain computed variable?
$: doubled = count * 2; is reactive: the compiler inspects the right-hand side, records which variables it depends on, and recomputes automatically when count changes — the same applies to a statement block such as $: { console.log(count); }. A plain const or let is evaluated once at declaration and never reruns. Reactive declarations must live at the top level of the component script and are ordered by their dependency graph. The common trap is a variable introduced inside the block becoming a dependency itself and looping; when that happens, wrap the part in question with untrack(() => { ... }) to stop dependency tracking.
When should shared state live in a Store instead of a component variable?
Reach for a store when state is shared across components or should be managed outside the component tree. Create one with import { writable } from "svelte/store", read it in components as $count — the subscription is automatic and torn down on destroy — and write with count.set(10) or count.update(n => n + 1) for an update based on the current value. Use readable for write-protected state and derived(count, ($c) => $c * 2) for values computed from other stores. Outside a component the $ prefix does not exist, so read the value with get(count).
How do slots and props divide the job of passing content into a child component?
Structured values travel as props: export let name declares what the component receives, export let name = "default" supplies a default, $$props exposes everything passed in and $$restProps only the ones you did not declare. Template content travels through <slot />, and when a child needs several insertion points use named slots such as <slot name="header" />. When the child needs to hand data back up to the parent, use a scoped slot: <slot let:item={item} />.
When do I need a lifecycle hook instead of just calling a function in the component?
Anything touching the DOM or a browser resource belongs in a hook. onMount(() => { ... }) runs after the component mounts, which is where an API request belongs; onDestroy(() => { ... }) runs before teardown and is where you clear timers and unsubscribe, preventing leaks; beforeUpdate and afterUpdate fire either side of a DOM update. await tick() lets you wait until the DOM has actually updated, e.g. to measure an input right after changing it. Pure computation needs none of this — a $: declaration is enough.
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Jul 21, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us