DevTools

Cheatsheet Svelte

Framework frontend compilado, leve e ultra performático

Back to languages
Svelte
75 cards found
Categories:
Versions:

Setup and Structure


8 cards
Create Project (SvelteKit)
npm create svelte@latest my-app
cd my-app
npm install
npm run dev

create svelte generates a complete SvelteKit project with routing, SSR and adapters. npm run dev starts the development server with hot reload at localhost:5173.

SvelteKit Structure
src/
  routes/        # pages and API
  lib/           # shared code
  app.html       # HTML template
static/          # static files
svelte.config.js # configuration

src/routes/ defines pages by files. src/lib/ holds reusable components and utils. svelte.config.js configures adapters, preprocessors and aliases.

Svelte Only (Vite)
npm create vite@latest my-app \
  -- --template svelte
cd my-app
npm install
npm run dev

Vite template without SvelteKit — just pure Svelte. Ideal for simple SPAs, widgets or libraries. No routing or SSR included.

svelte.config.js
import adapter from "@sveltejs/adapter-auto";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";

export default {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter(),
    alias: { "@": "./src" }
  }
};

preprocess allows using TypeScript, SCSS, etc. adapter defines the deploy target (Node, Vercel, static). alias creates import shortcuts like @/lib.

SFC Structure
<script>
  // JavaScript logic
  let name = "World";
</script>

<!-- HTML template -->
<h1>Hello, {name}!</h1>

<style>
  /* styles with automatic scope */
  h1 { color: tomato; }
</style>

An SFC (Single File Component) has 3 blocks: <script> (logic), HTML (template) and <style> (scoped CSS). The CSS only affects the component itself.

TypeScript
<script lang="ts">
  let counter: number = 0;
  let name: string = "Anna";

  interface User {
    name: string;
    age: number;
  }
  let user: User = { name: "Anna", age: 30 };
</script>

lang="ts" enables TypeScript in the script. Works with vitePreprocess() in the config. Types, interfaces and generics are supported directly in the SFC.

main.js (Mounting)
import App from "./App.svelte";

const app = new App({
  target: document.body,
});

export default app;

The entry file instantiates the root component with new App(). target defines the DOM element where the app is mounted. In SvelteKit, this is generated automatically.

Global Styles
<style>
  /* this component only */
  p { color: blue; }

  /* global (affects children) */
  :global(.card) { padding: 1rem; }

  /* partial scope */
  div :global(span) { font-weight: bold; }
</style>

By default, CSS is scoped to the component. :global() removes the scope for specific selectors. Useful for styling slot content or external libraries.

Reactivity


9 cards
Reactive Variables
<script>
  let counter = 0;

  function increment() {
    counter += 1;
  }
</script>

<button on:click={increment}>
  {counter}
</button>

In Svelte, any let in the <script> is reactive. Just reassign (counter += 1) and the DOM updates automatically. No need for setState or setters.

$state (Svelte 5)
<script>
  let counter = $state(0);
  let user = $state({ name: "Anna", age: 30 });

  // direct mutation works:
  user.age = 31;
  counter++;
</script>

$state() is Svelte 5's new reactivity rune. It creates deeply reactive state — mutations on nested properties trigger updates without reassignment.

$bindable (Svelte 5)
<!-- Child.svelte -->
<script>
  let { value = $bindable() } = $props();
</script>
<input bind:value={value} />

<!-- Parent.svelte -->
<Child bind:value={name} />

$bindable() marks a prop the bindable (two-way). The parent uses bind:value to sync. Replaces the old dispatch + export let pattern.

Reactive Declarations ($:)
let price = 10;
let qty = 2;

$: total = price * qty;
$: console.log("Changed:", total);
$: if (total > 100) {
  alert("Expensive!");
}

$: creates reactive declarations that re-run when dependencies change. Works the a derived value, side effect or reactive conditional block.

$derived (Svelte 5)
<script>
  let price = $state(10);
  let qty = $state(2);

  let total = $derived(price * qty);
  let withVat = $derived.by(() => {
    return total * 1.23;
  });
</script>

$derived() replaces $: for computed values. $derived.by() accepts a function for complex logic. Recomputes automatically when dependencies change.

Arrays and Immutability
let items = [1, 2, 3];

// reactivity requires reassignment:
items = [...items, 4];
items = items.filter(n => n > 1);

// objects:
let user = { name: "Anna" };
user = { ...user, age: 30 };

Svelte detects reassignment, not deep mutation. push() alone doesn't update — use spread ([...arr, x]) and reassign. In Svelte 5 with $state, direct mutation works.

$effect (Svelte 5)
<script>
  let counter = $state(0);

  $effect(() => {
    console.log("Counter:", counter);
    return () => {
      console.log("cleanup");
    };
  });
</script>

$effect() replaces $: for side effects. Runs after the DOM updates. The return (cleanup function) executes before the next run or on destroy.

Reactive Block
$: {
  console.log(a, b);
  result = a + b;
  if (result > 10) {
    message = "Big";
  }
}

A $: { } block groups multiple reactive statements. It re-runs when any dependency (a, b) changes. Useful for complex derived logic.

$props (Svelte 5)
<script>
  let { title, color = "blue", ...rest } = $props();
</script>

<h2 style="color: {color}">{title}</h2>

$props() replaces export let to receive props. Uses destructuring with defaults. ...rest captures extra props (spread). More explicit and type-safe.

Template e Binding


9 cards
Interpolation
<p>{name}</p>
<p>{1 + 1}</p>
<p>{active ? "Yes" : "No"}</p>
<p>{items.length} items</p>

Curly braces {} insert JavaScript expressions into the template. They accept any valid expression: variables, operations, ternaries, function calls. The output is escaped automatically.

Two-way Binding
<input bind:value={name} />
<input type="checkbox" bind:checked={active} />
<select bind:value={country}>
  <option>PT</option>
  <option>BR</option>
</select>
<input type="range" bind:value={volume} />

bind:value syncs the input with a variable in both directions. Works with text, checkbox, select, range. Eliminates manual on:input handlers.

Classes and Styles
<div class:active={isActive}>
<div class:visible>

<div style:color={color} style:font-size="14px">

<!-- Svelte 5: -->
<div class="base" class:active={flag}>

class:name={condition} adds/removes classes dynamically. style:prop={value} sets reactive inline styles. Cleaner than interpolating class strings.

If / else
{#if active}
  <p>Visible</p>
{:else if other}
  <p>Other state</p>
{:else}
  <p>Hidden</p>
{/if}

{#if} / {:else if} / {:else} / {/if} blocks do conditional rendering. The content is removed from the DOM (not just hidden) when false.

Element Bindings
<div bind:clientWidth={w} bind:clientHeight={h}>
  {w}x{h}
</div>

<input bind:this={inputEl} />
<script>
  import { onMount } from "svelte";
  onMount(() => inputEl.focus());
</script>

bind:clientWidth / clientHeight bind element dimensions to variables. bind:this stores the direct DOM reference to call methods like focus().

Each (Lists)
{#each items the item, i}
  <li>{i}: {item.name}</li>
{:else}
  <p>Empty list</p>
{/each}

{#each items the item (item.id)}
  <li>{item.name}</li>
{/each}

{#each} iterates arrays. The second parameter is the index. {:else} shows a fallback if empty. The key in parentheses (item.id) optimizes reordering.

Group Bindings
let flavors = [];
{#each ["vanilla", "chocolate", "strawberry"] the flavor}
  <label>
    <input type="checkbox" bind:group={flavors} value={flavor} />
    {flavor}
  </label>
{/each}
<p>Chosen: {flavors.join(", ")}</p>

bind:group syncs an array (checkboxes) or a value (radios) automatically. Each input with the same group shares the variable. No manual handlers.

Await
{#await fetch("/api/data")}
  <p>Loading...</p>
{:then response}
  <p>{response.ok}</p>
{:catch error}
  <p>Error: {error.message}</p>
{/await}

{#await} renders the states of a Promise: loading, success (:then) and error (:catch). Eliminates the need for manual loading/error variables.

Raw HTML and @const
{@html htmlContent}

{#each items the item}
  {@const upper = item.name.toUpperCase()}
  <p>{upper}</p>
{/each}

{@html} inserts HTML without escaping (XSS risk — sanitize first!). {@const} declares local variables inside {#each} or {#if} blocks.

Events and Actions


8 cards
on:click and Handlers
<button on:click={handler}>Function</button>
<button on:click={() => n++}>Inline</button>
<button on:click={fn1} on:click={fn2}>
  Multiple
</button>

on:click listens to DOM events. Accepts a function reference or an inline arrow function. Multiple handlers on the same event are allowed and run in order.

Listening to Child Events
<Child on:saved={onSave} />

<script>
  function onSave(e) {
    console.log(e.detail.id);
  }
</script>

The parent uses on:eventName to listen to events dispatched by the child. The payload is in e.detail. One-way communication: the child emits, the parent reacts.

Event Modifiers
<button on:click|once={fn}>Once</button>
<form on:submit|preventDefault={fn}>
<div on:click|stopPropagation={fn}>
<input on:keydown|self={fn}>

Modifiers with |: once (removed after 1st), preventDefault, stopPropagation, self (only if target is itself), capture, passive.

Event Forwarding
<!-- Wrapper.svelte -->
<button on:click>
  <slot />
</button>

<!-- Usage -->
<Wrapper on:click={handler}>
  Click here
</Wrapper>

on:click without a handler forwards the event to the parent. The wrapper component becomes transparent. Useful for UI components that wrap native elements.

Event Object
<input on:input={(e) => {
  value = e.target.value;
}} />

<div on:keydown={(e) => {
  if (e.key === "Enter") send();
}}>

The e parameter is the native DOM event. e.target.value accesses the input value. e.key identifies the key. Works with any event (click, keydown, scroll).

Actions (use:)
<input use:autofocus />

<script>
  function autofocus(node) {
    node.focus();
    return {
      update(params) { },
      destroy() { }
    };
  }
</script>

use:action runs a function when the element is created in the DOM. It receives the node and can return update (params change) and destroy (cleanup). Ideal for tooltips, focus, drag.

createEventDispatcher
<script>
  import { createEventDispatcher } from "svelte";
  const dispatch = createEventDispatcher();

  function save() {
    dispatch("saved", { id: 1 });
  }
</script>

createEventDispatcher() creates a custom event emitter. dispatch("name", data) sends to the parent. The parent listens with on:saved={handler}. Data lives in e.detail.

Actions with Parameters
<div use:tooltip={{ text: "Help", pos: "top" }}>
  Info
</div>

<script>
  function tooltip(node, { text, pos }) {
    // create tooltip with text and pos
    return {
      update({ text, pos }) { },
      destroy() { /* remove */ }
    };
  }
</script>

use:action={params} passes an object the the second argument. update() is called when the params change reactively. Enables configurable, reusable actions.

Components


9 cards
Props (export let)
<!-- Card.svelte -->
<script>
  export let title;
  export let color = "blue";
</script>

<h2 style="color: {color}">{title}</h2>

export let declares a prop received from the parent. The default value (= "blue") makes it optional. Without a default, the prop is required (warning if not passed).

Named Slots
<!-- Card.svelte -->
<slot name="header" />
<slot />
<slot name="footer" />

<!-- usage -->
<Card>
  <svelte:fragment slot="header">
    Title
  </svelte:fragment>
  Body
</Card>

slot name="x" creates named slots. The parent uses svelte:fragment slot="x" to fill them. The unnamed slot is the default. Enables complex layouts with multiple zones.

svelte:self and svelte:element
<!-- recursion -->
{#if depth > 0}
  <svelte:self depth={depth - 1} />
{/if}

<!-- dynamic element -->
<svelte:element this={tag}>
  Content
</svelte:element>

svelte:self enables recursion (trees, nested menus). svelte:element renders a dynamic HTML tag (h1, p, div) defined by a variable.

Using a Component
<script>
  import Card from "./Card.svelte";
</script>

<Card title="Hello" color="red" />
<Card title="World" />

Import the component and use it the an HTML tag. Props are passed the attributes. The tag name must be PascalCase (distinguishes it from native HTML elements). The import can use a relative path or the $lib alias.

Slot Props
<!-- List.svelte -->
{#each items the item}
  <slot {item} index={i} />
{/each}

<!-- usage -->
<List let:item let:index>
  <p>{index}: {item.name}</p>
</List>

Props on the <slot> expose child data to the parent. The parent accesses them with let:prop. The "render props" pattern — the child controls the data, the parent controls the rendering.

Props Spread
<script>
  const data = { title: "Hello", color: "red" };
</script>

<Card {...data} />
<Card {...data} color="blue" />

{...object} spreads all properties the props. Explicit props after the spread override it. Useful for passing lots of data or forwarding unknown props.

setContext / getContext
// parent
import { setContext } from "svelte";
setContext("theme", "dark");

// child (any depth)
import { getContext } from "svelte";
const theme = getContext("theme");

setContext / getContext share data without prop drilling. The context is set in the parent and accessible in any descendant. Ideal for themes, configs, feature stores.

Slots
<!-- Card.svelte -->
<div class="card">
  <slot>Default content</slot>
</div>

<!-- usage -->
<Card>My custom text</Card>
<Card />

<slot> defines where the parent's content is projected. The content between the tags is the fallback if nothing is passed. Enables flexible composition without props.

svelte:component
<svelte:component this={currentComponent} />

<script>
  import A from "./A.svelte";
  import B from "./B.svelte";
  let currentComponent = A;
</script>

svelte:component dynamically renders the component in this. When the variable changes, the old component is destroyed and the new one mounted. Useful for tabs, wizards.

Life Cycle and Transitions


8 cards
onMount
import { onMount } from "svelte";

onMount(() => {
  console.log("Mounted in the DOM!");
  const timer = setInterval(tick, 1000);

  return () => {
    clearInterval(timer);
  };
});

onMount runs after the component is inserted into the DOM. The return (a function) is the cleanup executed on destroy. Ideal for timers, subscriptions, data fetching.

Custom Transitions
function typewriter(node, { speed = 50 }) {
  const text = node.textContent;
  return {
    duration: text.length * speed,
    tick: (t) => {
      node.textContent = text.slice(0,
        Math.floor(t * text.length));
    }
  };
}

A custom transition returns duration and tick(t) where t goes from 0 to 1. tick is called every frame with the progress. Enables fully custom animations.

beforeUpdate / afterUpdate
import { beforeUpdate, afterUpdate } from "svelte";

beforeUpdate(() => {
  // before any DOM update
});

afterUpdate(() => {
  // after the DOM is updated
});

beforeUpdate runs before each re-render. afterUpdate runs after the DOM is updated. Useful for syncing external libraries or measuring elements.

Animate (flip)
import { flip } from "svelte/animate";

{#each items the item (item.id)}
  <div animate:flip={{ duration: 300 }}>
    {item.name}
  </div>
{/each}

animate:flip animates list reordering (First-Last-Invert-Play). Requires a key in the {#each}. When items change position, the transition is smooth.

onDestroy and tick
import { onDestroy, tick } from "svelte";

onDestroy(() => {
  console.log("Component removed");
});

// wait for the DOM update:
value = "new";
await tick();
// DOM already reflects "new"

onDestroy runs when the component is removed. tick() returns a Promise that resolves after the next DOM update — useful for accessing updated values.

Key Block
{#key value}
  <div transition:fade>
    Content for {value}
  </div>
{/key}

{#key expression} destroys and recreates the content when the expression changes. Useful for restarting animations or components without svelte:component. Combines well with transitions.

Transitions (fade, fly, slide)
import { fade, fly, slide } from "svelte/transition";

{#if visible}
  <div transition:fade={{ duration: 300 }}>
    Hello
  </div>
  <div in:fly={{ y: 200 }} out:slide>
    Animated
  </div>
{/if}

transition: animates enter and exit. in: / out: separate the animations. Built-ins: fade, fly, slide, scale, blur. They accept duration, delay, easing.

Crossfade
import { crossfade } from "svelte/transition";
const [send, receive] = crossfade({
  duration: 300
});

<!-- element A -->
<div out:send={{ key: id }} />

<!-- element B -->
<div in:receive={{ key: id }} />

crossfade creates send/receive pairs to animate elements between positions (e.g. list → detail). The same key links both elements in the transition.

Stores


8 cards
Writable Store
// stores.js
import { writable } from "svelte/store";
export const counter = writable(0);

// component
counter.set(5);
counter.update(n => n + 1);

writable(value) creates a mutable store. set() replaces the value. update() receives the current value and returns the new one. Any component can read/write.

Custom Store
function createCounter(initial) {
  const { subscribe, set, update } = writable(initial);
  return {
    subscribe,
    increment: () => update(n => n + 1),
    decrement: () => update(n => n - 1),
    reset: () => set(initial),
  };
}

Expose only subscribe + custom methods. Encapsulates the logic and prevents direct set(). The consumer uses $store normally. The pattern for stores with business rules.

Auto-subscription ($)
<script>
  import { counter } from "./stores";
</script>

<p>{$counter}</p>
<button on:click={() => $counter++}>
  Increment
</button>

The $ prefix subscribes to the store automatically. {$counter} in the template updates in real time. $counter++ works the a shortcut for update. Unsubscription is automatic.

Manual Subscribe
import { counter } from "./stores";

const unsub = counter.subscribe(value => {
  console.log("New:", value);
});

// when no longer needed:
unsub();

subscribe(callback) registers a listener and returns the unsubscribe function. Outside Svelte components (plain JS), there is no $ auto-subscription — use this method manually.

Readable Store
import { readable } from "svelte/store";

export const time = readable(new Date(), (set) => {
  const id = setInterval(
    () => set(new Date()), 1000
  );
  return () => clearInterval(id);
});

readable() creates a read-only store. The start function receives set to update. The return is the cleanup (runs when there are no subscribers). Ideal for clocks, sensors, WebSocket.

Store with localStorage
import { writable } from "svelte/store";

function persisted(key, initial) {
  const stored = localStorage.getItem(key);
  const store = writable(stored ? JSON.parse(stored) : initial);
  store.subscribe(v =>
    localStorage.setItem(key, JSON.stringify(v))
  );
  return store;
}

Combine writable with localStorage for persistence. The store hydrates from storage on creation and saves on every change. Common pattern for user preferences.

Derived Store
import { derived } from "svelte/store";
import { counter } from "./stores";

export const double = derived(
  counter,
  ($counter) => $counter * 2
);

// multiple stores:
export const sum = derived(
  [a, b], ([$a, $b]) => $a + $b
);

derived() creates a store computed from others. It recomputes automatically when the source changes. Accepts one or multiple stores the dependencies.

get() (Outside Components)
import { get } from "svelte/store";
import { counter } from "./stores";

const value = get(counter);
console.log(value); // current value

get(store) reads the current value without subscribing. Useful in utility functions, tests or code outside components. Not reactive — just a one-off read.

SvelteKit


9 cards
File-based Routing
src/routes/
  +page.svelte        // /
  about/
    +page.svelte      // /about
  blog/[slug]/
    +page.svelte      // /blog/:slug

Each folder in src/routes/ is a route. +page.svelte is the page. [slug] is a dynamic parameter. The folder structure defines the URLs automatically.

Layout
<!-- +layout.svelte -->
<nav>Global menu</nav>
<slot />
<footer>Footer</footer>

<!-- +layout.js -->
export const prerender = true;

+layout.svelte wraps all pages in the folder (and subfolders). The <slot /> is where the page renders. Layouts nest hierarchically. prerender generates static HTML.

Adapters and Deploy
// svelte.config.js
import adapter from "@sveltejs/adapter-node";
// or: adapter-auto, adapter-static,
//     adapter-vercel, adapter-cloudflare

export default {
  kit: { adapter: adapter() }
};

// build:
npm run build

The adapter defines the deploy target. adapter-node (Node server), adapter-static (static site), adapter-vercel/adapter-cloudflare (edge). npm run build generates the output.

Load Function
// +page.js
export async function load({ params, fetch }) {
  const res = await fetch(`/api/posts/${params.slug}`);
  return { post: await res.json() };
}

// +page.svelte
<script>
  export let data;
</script>
<h1>{data.post.title}</h1>

load() in +page.js loads data before rendering. Runs on client and server. The return is available in data. params gives access to the route parameters.

API Endpoints
// +server.js
export async function GET({ params }) {
  return new Response(
    JSON.stringify({ ok: true }),
    { headers: { "Content-Type": "application/json" } }
  );
}

export async function POST({ request }) {
  const body = await request.json();
  return new Response(null, { status: 201 });
}

+server.js creates HTTP endpoints. Export functions named after the verbs: GET, POST, PUT, DELETE. Returns a native Response. Ideal for REST APIs.

Server Load
// +page.server.js
export async function load({ params }) {
  const post = await db.posts.find(params.slug);
  return { post };
}

+page.server.js runs only on the server. Ideal for accessing the DB, secrets or private APIs. Data is serialized and sent to the client. Never exposes sensitive code.

Hooks
// hooks.server.js
export async function handle({ event, resolve }) {
  const token = event.cookies.get("token");
  event.locals.user = await getUser(token);
  return resolve(event);
}

// in +page.server.js:
export function load({ locals }) {
  return { user: locals.user };
}

hooks.server.js intercepts all requests. handle runs before each route. event.locals shares data between hooks and load. Ideal for global auth, logging, CORS.

Form Actions
// +page.server.js
export const actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const name = data.get("name");
    await db.save({ name });
    return { success: true };
  }
};

actions process form submissions on the server. request.formData() reads the fields. The return goes into form on the page. Supports progressive enhancement (works without JS).

Programmatic Navigation
<script>
  import { goto, invalidupToll } from "$app/navigation";
  import { page } from "$app/stores";

  async function logout() {
    await fetch("/logout", { method: "POST" });
    invalidupToll();
    goto("/login");
  }
</script>
<p>Current route: {$page.url.pathname}</p>

goto() navigates programmatically. invalidupToll() re-runs all the load() functions. $page is a store with URL, params and status. Replaces window.location.

Advanced


7 cards
svelte:window and svelte:body
<svelte:window on:scroll={onScroll}
  bind:scrollY={y} />
<svelte:body on:mouseenter={fn} />
<svelte:head>
  <title>{title} | App</title>
</svelte:head>

svelte:window listens to global events without a manual addEventListener. bind:scrollY syncs the scroll. svelte:head injects tags into the <head> (SEO, meta tags).

Streaming and Defer
// +page.server.js
export function load() {
  return {
    fast: { title: "Hello" },
    slow: db.query("SELECT ...")  // Promise
  };
}

// +page.svelte
{#await data.slow}
  <p>Loading slow data...</p>
{:then result}
  <p>{result.length} records</p>
{/await}

Returning Promises (without await) in load enables streaming — the page renders immediately with the fast data. {#await} in the template shows a loading state until resolved.

Snippets (Svelte 5)
{#snippet card(item)}
  <div class="card">
    <h3>{item.title}</h3>
    <p>{item.text}</p>
  </div>
{/snippet}

{#each items the item}
  {@render card(item)}
{/each}

{#snippet} defines reusable template blocks (replaces slot props). {@render} invokes the snippet with arguments. More explicit and composable than named slots.

Testing (Vitest)
import { render, screen } from "@testing-library/svelte";
import { test, expect } from "vitest";
import Card from "./Card.svelte";

test("shows title", () => {
  render(Card, { props: { title: "Hello" } });
  expect(screen.getByText("Hello")).toBeTruthy();
});

@testing-library/svelte renders components in tests. render(Component, { props }) mounts it in the virtual DOM. screen.getByText finds elements. Runs with vitest.

Accessibility (a11y)
<!-- Svelte warns at compile time: -->
<img src="photo.jpg" alt="Description" />
<button aria-label="Close">×</button>
<input aria-invalid={!!error} />

<!-- svelte-ignore to suppress -->
<!-- svelte-ignore a11y-missing-content -->

Svelte emits accessibility warnings at compile time: alt on images, labels on inputs, roles. svelte-ignore suppresses specific warnings when needed.

Performance and Best Practices
<!-- keyed each for large lists -->
{#each items the item (item.id)}
  <Item {item} />
{/each}

<!-- lazy load components -->
<script>
  import { onMount } from "svelte";
  let Heavy;
  onMount(async () => {
    Heavy = (await import("./Heavy.svelte")).default;
  });
</script>

Use a key in {#each} for efficient diffs. Dynamic import() does code-splitting. Svelte compiles to vanilla JS — no virtual DOM, surgical updates to the real DOM.

Error Boundaries
<!-- +error.svelte -->
<script>
  import { page } from "$app/stores";
</script>

<h1>{$page.status}: {$page.error.message}</h1>
<a href="/">Back to home</a>

+error.svelte renders when a route throws an error. $page.status and $page.error give the details. Can exist per route or globally in src/routes/.