Compiler Framework

Svelte

Svelte is a compiler, not a runtime. It turns your components into efficient DOM code at build time, so the browser ships less JavaScript and does less work.

intermediate14 min readUpdated Sep 15, 2026
Counter.svelte
svelte
<!-- Counter.svelte -->
<script>
  let count = $state(0);
</script>

<button onclick={() => count++}>
  Clicked {count} times
</button>
First release
2016, by Rich Harris
Model
Compiler, no virtual DOM
Current major
Svelte 5
Reactivity
Runes ($state, $derived)
Components
.svelte single-file
App framework
SvelteKit

Why it matters

Why Svelte feels different

Compiled, not interpreted

The framework disappears at build time, leaving small, direct DOM updates instead of a runtime diffing layer.

Less code to write

Reactivity, scoped styles and transitions are built into the language, so components stay short and readable.

Small and fast

No virtual DOM and a tiny runtime mean excellent startup performance, even on low-powered devices.

The big picture

The three ideas behind Svelte

A compiler, fine-grained updates and a component format that keeps markup, logic and styles together.

The compiler

Build time

Svelte analyses your components and emits optimised JavaScript that updates the DOM directly.

Reactivity

Data

Runes such as $state, $derived and $effect make values reactive and derived values automatic.

The component format

Authoring

A .svelte file holds markup, logic and scoped styles together, with transitions and animations built in.

Svelte at a glance

The core of Svelte

Single-file components

Markup, script and scoped styles in one .svelte file.

$state

The rune that declares reactive local state.

$derived and $effect

Compute values automatically or run side effects when state changes.

Props and snippets

Pass data down with $props and reuse markup with snippets.

Stores

Share reactive state across components with writable and readable stores.

Transitions

Built-in transition and animation helpers bring motion without extra libraries.

A short history

From side project to compiler-first framework

  1. 2016

    Svelte 1

    Rich Harris introduces a compiler that shifts framework work from runtime to build time.

    16
  2. 2019

    Svelte 3

    A rewrite around reactivity and the dollar-label syntax makes components dramatically simpler.

    19
  3. 2020

    SvelteKit

    An official app framework adds routing, server rendering and adapters.

    20
  4. 2024

    Svelte 5

    Runes replace implicit reactivity with explicit, composable primitives.

    24
  5. Today

    A mature alternative

    Svelte is a mainstream choice for performance-sensitive and content-heavy sites.

    Today

The complete guide

Svelte: Everything you need to know

What is Svelte?

Svelte is a component framework with a twist: it is a compiler. Instead of shipping a runtime that interprets your components in the browser, Svelte analyses them at build time and emits plain JavaScript that updates the DOM directly. There is no virtual DOM and almost no framework code in the final bundle.

That design changes the developer experience. Reactivity is part of the language, styles are scoped by default, and transitions are built in, so components end up short. The trade-off is that you need a build step, but modern tooling makes that a given.

Single-file components

A Svelte component is a .svelte file with a script block, markup and optional scoped styles.

<!-- Card.svelte -->
<script>
  let { title, children } = $props();
</script>

<article class="card">
  <h3>{title}</h3>
  {@render children?.()}
</article>

<style>
  .card {
    border-radius: 1rem;
    padding: 1.5rem;
  }
</style>

Styles in a component are scoped to it by default, so the .card rule cannot leak. $props() reads the props passed by the parent, and snippets (children) let a parent pass markup into a child.

Reactivity with runes

Svelte 5 introduced runes, explicit primitives that make reactivity predictable. $state declares reactive state, $derived computes values, and $effect runs side effects.

<!-- Search.svelte -->
<script>
  let query = $state("");
  let results = $state([]);

  let hasResults = $derived(results.length > 0);

  $effect(() => {
    if (!query) return;
    const controller = new AbortController();
    fetch(`/api/search?q=${query}`, { signal: controller.signal })
      .then((r) => r.json())
      .then((data) => (results = data))
      .catch(() => {});
    return () => controller.abort();
  });
</script>

<input bind:value={query} placeholder="Search" />

{#if hasResults}
  <ul>
    {#each results as item (item.id)}
      <li>{item.name}</li>
    {/each}
  </ul>
{/if}

Runes work in .svelte.js and .svelte.ts files too, so you can extract reactive logic into reusable modules the same way React uses custom hooks.

Props, events and snippets

Props flow down and are declared with $props(). Instead of a special event system, Svelte 5 uses callback props for child-to-parent communication.

<!-- Child.svelte -->
<script>
  let { label, onselect } = $props();
</script>

<button onclick={() => onselect(label)}>{label}</button>
<!-- Parent.svelte -->
<Child label="Save" onselect={(value) => save(value)} />

Snippets replace slots for reusable markup regions, and {@render} inserts them where you want.

Control flow

Svelte has dedicated template blocks for conditionals and loops, and the #each block supports a keyed expression for efficient updates.

<!-- list.svelte -->
{#if users.length > 0}
  <ul>
    {#each users as user (user.id)}
      <li>{user.name}</li>
    {/each}
  </ul>
{:else}
  <p>No users yet.</p>
{/if}

Because the compiler knows the structure, these blocks generate targeted DOM operations rather than a generic reconciliation pass.

Stores and shared state

For state shared across many components, Svelte provides stores: a writable store holds a value and notifies subscribers, and readable and derived cover other cases.

// counter.js
import { writable, derived } from "svelte/store";

export const count = writable(0);
export const double = derived(count, ($count) => $count * 2);
<!-- Display.svelte -->
<script>
  import { count, double } from "./counter.js";
</script>

<p>{$count} doubled is {$double}</p>

The $ prefix auto-subscribes and unsubscribes for you. With runes, a .svelte.js module exporting $state can achieve the same thing without the store API, which many new projects prefer for application-wide values.

Transitions and animations

Motion is built in. The transition, animate and in/out directives add movement without an external library.

<!-- toast.svelte -->
<script>
  import { fade, fly } from "svelte/transition";
  let visible = $state(false);
</script>

<button onclick={() => (visible = !visible)}>Toggle</button>

{#if visible}
  <div transition:fly={{ y: 20, duration: 200 }}>
    Saved!
  </div>
{/if}

Because transitions are compiled, they are efficient and tree-shakeable, and they respect the user’s reduced-motion preference when you use the built-in helpers.

SvelteKit

SvelteKit is the official application framework. It adds file-based routing, server rendering, data loading and form actions on top of Svelte.

src/routes/
├── +page.svelte        # a page
├── +layout.svelte      # shared layout
├── about/+page.svelte  # /about
└── blog/[slug]/+page.js # load data for /blog/:slug

If you are building more than a widget, SvelteKit is the recommended starting point. It handles the conventions that every real app needs so you can focus on features.

Best practices

  • Use runes ($state, $derived, $effect) in new code.
  • Prefer $derived for computed values and reserve $effect for side effects.
  • Keep components small and extract reusable logic into .svelte.js modules.
  • Use keyed {#each} blocks when items can reorder.
  • Scope styles by default and avoid :global unless necessary.
  • Share cross-component state with stores or a reactive module.
  • Start new apps with SvelteKit rather than wiring a build yourself.

Common mistakes

  • Using $effect to compute a value that $derived should handle.
  • Forgetting the key in {#each} and breaking list updates.
  • Mutating a $state object’s nested property in a way that bypasses reactivity in older patterns.
  • Reaching for a global store when local state would do.
  • Assuming Svelte has no runtime at all — there is a small one.
  • Ignoring accessibility because the compiler makes markup easy to write quickly.

Where to go next

Svelte is a fast, elegant way to build interfaces, and SvelteKit turns it into a full application framework. Compare the compiler approach with React and Vue, and keep your JavaScript sharp. Then build a small app with a store and a transition to feel the difference.

Declaring reactivity

Svelte 5 makes reactivity explicit with runes, which works in any .js or .ts file, not only inside components.

Svelte 5
<script>
  let count = $state(0);
  let double = $derived(count * 2);
</script>
Legacy
<script>
  let count = 0;
  $: double = count * 2;
</script>

Derived values

Use $derived for values computed from state, and $effect only for genuine side effects.

Prefer
let items = $state([1, 2, 3]);
let total = $derived(
  items.reduce((a, b) => a + b, 0),
);
Avoid
let total = $state(0);
$effect(() => {
  total = items.reduce(
    (a, b) => a + b, 0,
  );
});

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Svelte?

Our interactive tutorial walks you through Svelte step by step — with quizzes and real code you can run in the browser.