Skip to main content

The Svelte Cheat Sheet I Wish I Had on Day One

A working developer's tour of Svelte essentials — reactive declarations, props, stores, each and if blocks, two-way binding, and why Svelte compiles itself away.

Published By Li Lei
#svelte #frontend #cheat-sheet #javascript #web-development

The Svelte Cheat Sheet I Wish I Had on Day One

Most frameworks ship a runtime to your users' browsers. Svelte does something different: it reads your components at build time and writes plain, surgical JavaScript that updates the DOM directly. There is no virtual DOM diff on every render, no framework library sitting in the bundle waiting to reconcile. By the time your code reaches a browser, the framework is gone — what runs is the output it generated for you.

That single idea explains why Svelte feels so small. A counter component compiles to a few lines that flip one text node when a number changes. This post walks through the syntax you actually reach for every day, with real snippets, one worked counter scenario, and a quick reference at the end. Keep the interactive Svelte cheat sheet open in a second tab so you can copy any line straight into your editor.

Reactive Declarations With the $: Label

The first thing that surprises people coming from React is that you do not call a setter. You assign to a variable, and Svelte makes the assignment reactive:

<script>
  let count = 0;
  $: doubled = count * 2;
  $: if (count > 10) console.log('big now');
</script>

The $: label marks a statement as reactive. Whenever count changes, every $: line that reads count re-runs. doubled is a derived value that recomputes on its own — you never assign to it directly anywhere else. The second line shows that $: is not limited to declarations; any statement, including an if block, can be reactive, which makes it a tidy place for side effects that depend on state.

The key mental shift: there is no dependency array to maintain. Svelte's compiler reads which variables each reactive block touches and wires the updates for you. You add a variable to the expression, it becomes a dependency automatically.

Props With export let

A component receives data through props, and in Svelte a prop is just an exported let:

<script>
  export let name;
  export let greeting = 'Hello';
</script>

<p>{greeting}, {name}!</p>

export let name declares a required prop; export let greeting = 'Hello' gives one a default. The parent passes them as attributes: <Card name="Lei" />. There is no separate props object to destructure and no schema to register — the export statement is the contract. Anything you export is settable from outside; anything you keep as a plain let stays private to the component.

Stores for State That Travels

When state needs to live outside a single component — auth, theme, a shopping cart — you reach for a store. A store is any object with a subscribe method, and Svelte ships the common ones:

<script>
  import { writable } from 'svelte/store';
  const cart = writable([]);

  function add(item) {
    cart.update((items) => [...items, item]);
  }
</script>

<p>{$cart.length} items in cart</p>

The magic is the $ prefix. Writing $cart inside a component reads the store's current value and subscribes to it; Svelte generates the subscribe and unsubscribe calls so you never leak a listener. You set values with cart.set(next) or transform them with cart.update(fn). There is also readable for values you only read (a clock, a geolocation feed) and derived for a store computed from other stores.

The each and if Blocks

Markup logic in Svelte lives in template blocks, not in JSX expressions. Conditionals use {#if}:

{#if user}
  <p>Welcome back, {user.name}</p>
{:else}
  <a href="/login">Sign in</a>
{/if}

Lists use {#each}, and you almost always want a key so Svelte can track items across reorders:

<ul>
  {#each todos as todo (todo.id)}
    <li>{todo.text}</li>
  {/each}
</ul>

The (todo.id) at the end is the key. Without it, Svelte falls back to index-based updates, which produce subtle bugs when you insert or remove items mid-list. There is also {#await} for rendering the three states of a promise (pending, resolved, rejected) right in the template, which removes a surprising amount of loading-flag boilerplate.

Two-Way Binding With bind:

Forms are where Svelte's bind: directive shines. Instead of wiring an oninput handler that reads the event and calls a setter, you bind the value directly:

<script>
  let email = '';
</script>

<input bind:value={email} />
<p>You typed: {email}</p>

Type into the input and email updates; change email in code and the input reflects it. The binding flows both ways. bind: works on checkboxes (bind:checked), selects, and even component props, and bind:this hands you a reference to the raw DOM element when you need to call .focus() or measure it.

A Worked Scenario: A Reactive Counter

Let me put the pieces together with the example I always start newcomers on, because it shows reactivity, derived state, and an event handler in nine lines:

<script>
  let count = 0;
  $: doubled = count * 2;
  $: parity = count % 2 === 0 ? 'even' : 'odd';
</script>

<button on:click={() => count += 1}>
  count is {count}
</button>
<p>doubled: {doubled} — that number is {parity}</p>

Here is what I noticed the first time I wrote this and inspected the build output: clicking the button does not re-run the component. Svelte compiled count += 1 into a tiny function that mutates one variable, marks it dirty, and updates exactly the three text nodes that depend on it — the button label, doubled, and parity. No diff, no re-render of unaffected nodes. When I traced through it in DevTools I finally understood what "compiles away the framework" means in practice: there was no framework call in the call stack at all, just my logic and a couple of generated set_data lines touching the DOM. That was the moment Svelte clicked for me.

doubled and parity are both derived with $:, so they stay correct without my touching them in the click handler. I only ever change count; everything downstream follows.

Quick Reference

| You want to… | Write | | --- | --- | | A local reactive variable | let count = 0; | | A derived value | $: doubled = count * 2; | | A reactive side effect | $: if (open) lockScroll(); | | A required prop | export let name; | | A prop with a default | export let size = 'md'; | | A writable store | const s = writable(0); | | Read a store in markup | {$s} | | Conditional markup | {#if cond} … {:else} … {/if} | | A keyed list | {#each items as item (item.id)} … {/each} | | Await a promise | {#await p} … {:then v} … {/await} | | Two-way input binding | <input bind:value={x} /> | | An element reference | <div bind:this={el} /> |

Print that table, pin it next to your monitor, and you have covered maybe eighty percent of what a real Svelte component uses. The rest — transitions, actions, snippets, the newer runes syntax in Svelte 5 — builds on these same primitives, and you can find every one of them with copyable examples in the full Svelte cheat sheet.

If you are coming from another framework and want to map these ideas onto familiar ground, the React hooks cheat sheet lines up $: derived state against useMemo and Svelte stores against React Context, which is the comparison that made both frameworks legible to me. Svelte's bet is that less ceremony at write time and less code at run time beats a clever runtime. After a few components, I think you will agree.


Made by Toolora · Updated 2026-06-13