mrkeyoor.com_
Wed 05 Aug 05:05 UTC
npmWeb Frontendupdated 05 Aug 2026

svelte

Svelte is a UI framework that works as a compiler: you write declarative components in .svelte files and it compiles them into plain JavaScript that updates the DOM directly, with no virtual DOM diffing at runtime. Since version 5, reactivity is explicit through 'runes' ($state, $derived, $effect), which replaced the older implicit let-and-$: reactivity. The result is small output, fast updates, and components that read close to plain HTML, CSS, and JS. Most real apps pair it with SvelteKit, its official app framework.

Verdict

Technically excellent and genuinely pleasant to write; with SvelteKit it is a complete, actively developed stack (87.8K stars, pushed this week). The honest tradeoff is ecosystem size and the Svelte 5 content gap: you trade React's endless libraries and answers for cleaner code you will write more of yourself.

API stability3/5Svelte 5 (late 2024) replaced the reactivity model wholesale; legacy syntax still compiles and a migration tool exists, but the ecosystem is mid-transition and old patterns are deprecated.
Docs5/5svelte.dev has a full interactive tutorial, live playground, and reference that is current for runes; among the best docs of any frontend framework.
Maintenance5/5Very active monorepo (pushed August 2, 2026, version 5.56.x cadence is near-weekly) with full-time maintainers funded via Open Collective and Vercel.
Ecosystem3/55.3M weekly downloads and a healthy SvelteKit ecosystem, but component libraries, integrations, and hiring depth are far behind React and Vue, and some libraries lag on Svelte 5 support.

Use it if

  • You want less framework ceremony: state is a plain variable with $state, templates are HTML-like, and styles are scoped per component by default
  • Runtime payload and update performance matter; compiled output is small (the runtime the compiler injects is around 10.8 KB gzipped) and updates are targeted, not diffed
  • You are building a content site or app with SvelteKit and want SSR, routing, and progressive enhancement handled by the official stack
  • Your team is small or includes people closer to HTML/CSS than to React idioms; the learning curve to productive is short
Skip it if

Setup reality

You never install just svelte in practice; you scaffold with npx sv create (SvelteKit) or a Vite template, which wires the compiler plugin for you. That part is smooth. The real friction in 2026 is the version 4 to 5 transition: docs and runes are current, but a large share of blog posts, Stack Overflow answers, and third-party components still show pre-runes syntax (export let, on:click, slots), so beginners constantly hit deprecated patterns. Runes only work in .svelte and .svelte.js/.svelte.ts files, which trips people up when they try to share reactive state from a plain .ts module.

Patterns

Declare reactive state with $statereactive-state

<script>
  let count = $state(0);
</script>

<button onclick={() => count++}>
  clicked {count} times
</button>

Runes like $state are compiler keywords, not imports; they only work in .svelte and .svelte.js/.svelte.ts files.

Compute a value from other statederived-value

<script>
  let count = $state(0);
  let doubled = $derived(count * 2);
  let big = $derived.by(() => {
    // multi-line derivations use $derived.by
    return count > 10 ? 'big' : 'small';
  });
</script>

This replaces Svelte 4's $: label syntax; $derived values are read-only and recompute lazily.

Run a side effect when state changesside-effect

<script>
  let query = $state('');
  $effect(() => {
    const id = setTimeout(() => search(query), 300);
    return () => clearTimeout(id); // cleanup runs before re-run and on destroy
  });
</script>

$effect tracks whatever it reads synchronously; values read inside setTimeout or await are not tracked, a common surprise.

Accept props with $propscomponent-props

<script>
  let { name, size = 'md', ...rest } = $props();
</script>

<span class={`badge-${size}`} {...rest}>{name}</span>

This replaces 'export let name' from Svelte 4; defaults and rest props come straight from destructuring.

Bind an input, and make a prop bindabletwo-way-binding

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

<!-- Parent.svelte -->
<script>
  import Input from './Input.svelte';
  let name = $state('');
</script>
<Input bind:value={name} />

In Svelte 5 a component prop must be declared $bindable() before a parent may bind: to it; plain props are one-way.

Handle DOM events and component callbackshandle-events

<script>
  let { onRemove } = $props(); // callback prop instead of createEventDispatcher
</script>

<button onclick={() => onRemove?.()}>remove</button>
<input oninput={(e) => console.log(e.currentTarget.value)} />

Svelte 5 uses plain event attributes (onclick, oninput); the on:click directive and createEventDispatcher are deprecated in favor of callback props.

Render a keyed listlist-rendering

<ul>
  {#each todos as todo (todo.id)}
    <li class:done={todo.done}>{todo.text}</li>
  {:else}
    <li>Nothing to do</li>
  {/each}
</ul>

The (todo.id) key matters: without it Svelte patches rows by index, which breaks animations and input state on reorder.

Conditionals and awaiting promises in markupconditional-and-async

{#if user}
  <p>Hello {user.name}</p>
{:else}
  <p>Signed out</p>
{/if}

{#await fetchPosts()}
  <p>loading...</p>
{:then posts}
  <p>{posts.length} posts</p>
{:catch err}
  <p>failed: {err.message}</p>
{/await}

{#await} re-runs if the promise expression changes; in SvelteKit, prefer load functions for data that needs SSR.

Pass markup to a component with snippetsreusable-markup-snippets

<!-- Card.svelte -->
<script>
  let { header, children } = $props();
</script>
<div class="card">
  {@render header?.()}
  {@render children?.()}
</div>

<!-- usage -->
<Card>
  {#snippet header()}<h2>Title</h2>{/snippet}
  <p>Body content becomes the children snippet</p>
</Card>

Snippets replace Svelte 4 slots; content not wrapped in a named {#snippet} arrives as the implicit children snippet.

Share reactive state across componentsshared-state-module

// counter.svelte.js
export const counter = $state({ value: 0 });

// AnyComponent.svelte
<script>
  import { counter } from './counter.svelte.js';
</script>
<button onclick={() => counter.value++}>{counter.value}</button>

The file must be named *.svelte.js or *.svelte.ts for runes to compile, and you must export an object and mutate its properties; reassigning an exported binding breaks reactivity.

Get a DOM node and run mount logicbind-element-lifecycle

<script>
  import { onMount } from 'svelte';
  let canvas; // populated via bind:this

  onMount(() => {
    const ctx = canvas.getContext('2d');
    ctx.fillRect(0, 0, 100, 100);
    return () => {/* cleanup on destroy */};
  });
</script>

<canvas bind:this={canvas} width="200" height="200"></canvas>

onMount still exists in Svelte 5 and only runs in the browser, which makes it the standard place for DOM-only code under SSR.

Alternatives

PackageRegistryPick it when
reactnpmYou want the largest ecosystem, hiring pool, and third-party component supply, and accept more boilerplate
vuenpmYou want a similar single-file-component feel with a bigger ecosystem and optional no-build usage
solid-jsnpmYou want fine-grained signal reactivity like runes but with JSX instead of a template compiler
preactnpmYou need React compatibility at a tiny size rather than a different component model