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.

Web Services·50 commands·Last updated 2026-07-21
svelteFrontendFrameworkComponents

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 }
Insert JavaScript expression in template
{@html rawHtmlString}
Render raw HTML (beware of XSS)
{#if condition}...{:else if}...{:else}...{/if}
Conditional rendering
{#each items as item, index}...{/each}
Iterate over arrays
{#await promise}...{:then value}...{:catch error}...{/await}
Handle async Promise states
{#key expression}...{/key}
Destroy and recreate content when expression changes

Reactive Declarations 5

let count = 0;
Declare a variable, assignment triggers view update
$: doubled = count * 2;
Reactive declaration, auto-recalculates on dependency changes
$: { console.log(count); }
Reactive statement block, executes when dependencies change
arr = [...arr, newItem];
Array update: assign new reference to trigger reactivity
obj = { ...obj, key: value };
Object update: assign new reference to trigger reactivity

Components & Props 7

export let name;
Declare a component prop
export let name = "default";
Prop with default value
$$props
Access all passed props
$$restProps
Access undeclared remaining props
<slot />
Default slot for child content
<slot name="header" />
Named slot
<slot let:item={item} />
Scoped slot, passing data to parent

Events & Bindings 7

<button on:click={handler}>
Bind DOM event
<form on:submit|preventDefault={handler}>
Event modifiers (preventDefault/stopPropagation)
import { createEventDispatcher } from "svelte"; const dispatch = createEventDispatcher(); dispatch("event", data);
Dispatch custom events from child to parent
<input bind:value={name}>
Two-way binding of input value
<input bind:this={inputEl}>
Bind DOM element reference
<input type="checkbox" bind:group={selected}>
Checkbox group binding to array
<input type="radio" bind:group={choice}>
Radio group binding

Lifecycle 6

import { onMount } from "svelte"; onMount(() => { ... });
Execute after component mounts (good for API calls)
import { beforeUpdate } from "svelte"; beforeUpdate(() => { ... });
Execute before DOM update
import { afterUpdate } from "svelte"; afterUpdate(() => { ... });
Execute after DOM update
import { onDestroy } from "svelte"; onDestroy(() => { ... });
Execute before component unmounts (cleanup timers/subscriptions)
import { tick } from "svelte"; await tick();
Wait for DOM to finish updating, get updated DOM state
import { untrack } from "svelte"; untrack(() => { ... });
Temporarily stop dependency tracking in reactive declarations

Store 7

import { writable } from "svelte/store"; export const count = writable(0);
Create a writable store
import { readable } from "svelte/store"; export const time = readable(new Date(), (set) => { ... });
Create a readable store (with initial value and set function)
import { derived } from "svelte/store"; export const doubled = derived(count, ($c) => $c * 2);
Create a derived store that depends on other stores
$count
Auto-subscribe and unsubscribe in components
count.set(10);
Directly set a store value
count.update(n => n + 1);
Update store value based on current value
import { get } from "svelte/store"; const val = get(count);
Get store value outside component context

Actions & Transitions 7

use:action={{ param }}
Custom action to manipulate DOM elements
transition:fade
Fade in/out transition
transition:slide
Slide expand/collapse transition
transition:scale
Scale transition
in:fly={{ x: 200, duration: 500 }}
Enter animation (fly in)
out:fly={{ x: -200 }}
Leave animation (fly out)
animate:flip
Animate list item position changes (FLIP)

Advanced Features 5

import { setContext, getContext } from "svelte"; setContext("key", value); const val = getContext("key");
Cross-level component communication (Context API)
import { hasContext } from "svelte"; hasContext("key");
Check if a context key exists
<script context="module">...</script>
Module-level script, runs once per component definition
export const greet = (name) => `Hello ${name}`;
Export functions from module script for external use
svelte:options
Compiler options, e.g. <svelte:options immutable={true} />

Parameter matrix

ParameterEffectExample
{ 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 * 2Reactive declaration, recomputed automatically when dependencies change$: total = price * qty
arr = [...arr, x]Array updates require assigning a new reference to trigger an updateitems = [...items, newItem]
obj = { ...obj, k: v }Object updates likewise require assigning a whole new referencestate = { ...state, open: true }
export let nameDeclare a prop the component receivesexport let name;
$$restPropsRead 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|preventDefaultBind a DOM event and use event modifiers<form on:submit|preventDefault={save}>
createEventDispatcher()Dispatch a custom event from child to parentdispatch('change', data)
bind:value / bind:thisTwo-way bind an input value / bind a DOM element reference<input bind:value={name} bind:this={inputEl} />
bind:groupBind a checkbox group to an array, a radio group to a variable<input type="checkbox" bind:group={selected} value={id} />
onMount / onDestroyRun a request after mount / clean up timers and subscriptions before destroyonDestroy(() => clearInterval(timer))
await tick()Wait for the DOM update to finish before reading element stateawait tick(); el.focus();
untrack(() => ...)Temporarily disable dependency tracking within a reactive declarationuntrack(() => log(count))
writable / readable / derivedCreate writable, readable, and derived Storesconst doubled = derived(count, (n) => n * 2)
get(store)Read the current value of a Store synchronously in a non-component contextconst val = get(count)
use:actionCustom action function to manipulate a DOM elementuse:tooltip
transition:fade / slide / scalePlay a transition animation when the element appears and disappearstransition:slide
in:fly={{ x: 200 }}Enter animation (fly in); out: is the matching leave animationin:fly={{ x: 200, duration: 500 }}
animate:flipTween animation (FLIP) when list elements change positionanimate:flip={{ duration: 200 }}
setContext / getContextCross-level component communication; hasContext checks whether one existssetContext('theme', 'dark')
svelte:optionsCompiler 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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}.

  6. 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.

  7. 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