Articles

Svelte 5 Runes Explained

Svelte 5 runes like $state and $derived replace the old reactive-assignment magic with explicit function calls that work anywhere in a file.

Takina Takina · · 4 min read
Code editor showing a frontend component

Runes are the reactivity primitives introduced in Svelte 5 — special function-like symbols ($state, $derived, $effect, and others) that mark which values in a component should be reactive. They replace Svelte’s earlier approach, where reactivity was inferred implicitly from let assignments and a $: label, with explicit declarations that behave consistently whether they’re used in a component, a plain .js file, or a shared module.

What problem runes solve

Svelte’s original reactivity model was famously terse: assigning to a top-level let variable in a component triggered a re-render, and $: doubled = count * 2 created a reactive computation. That syntax was compact but had sharp edges — it only worked inside .svelte files, it depended on the compiler recognizing specific assignment patterns, and reactive state couldn’t easily be extracted into a reusable function the way a React hook or a Vue composable could.

Runes fix this by making reactivity an explicit, portable primitive rather than a compiler inference. A value wrapped in $state() is reactive no matter which file it lives in, which means reactive logic can finally be factored out into ordinary .svelte.js or .svelte.ts modules and imported wherever it’s needed — something closer to how React’s useMemo and useCallback or Vue’s composables let you share stateful logic across components.

The core runes

  • $state — declares a reactive value. let count = $state(0) creates a value that, when reassigned, triggers updates anywhere it’s read.
  • $derived — computes a value from other reactive state, recalculating automatically when its dependencies change. It replaces the old $: computed = ... pattern with a direct expression: let doubled = $derived(count * 2).
  • $effect — runs a side effect whenever its reactive dependencies change, similar in spirit to a useEffect dependency array, but with dependencies tracked automatically rather than declared in an array.
  • $props — declares a component’s props, replacing the older export let syntax with a single destructuring call: let { name, age } = $props().

These four cover the vast majority of component logic. A few more specialized runes ($state.raw, $bindable, $inspect) handle less common cases like opting out of deep reactivity for large objects or enabling two-way binding on a prop.

Reactivity as a general primitive, not a framework signal

The broader idea behind runes — wrapping a value so that reads are tracked and writes trigger updates — is the same idea behind signals as a frontend state primitive, which several frameworks have converged on independently in recent years. What makes Svelte’s version distinct is that the compiler still does real work: runes are compiled away into fine-grained update code rather than shipped as a runtime reactivity library, which is part of why Svelte components tend to produce comparatively little client-side JavaScript.

That compilation step is also why runes only work in files the Svelte compiler processes — a .svelte file, or a .svelte.js/.svelte.ts module explicitly opted into compilation. A plain .js file with no Svelte tooling watching it won’t understand $state at all; it isn’t a real function, just a token the compiler recognizes and rewrites during the build step, which distinguishes it from a normal JavaScript API you could pass a value through at runtime.

An example

<script>
  let count = $state(0);
  let doubled = $derived(count * 2);

  $effect(() => {
    console.log(`count is now ${count}`);
  });
</script>

<button onclick={() => count++}>
  {count} doubled is {doubled}
</button>

Clicking the button increments count, which recomputes doubled and re-runs the effect — all without a $: label or an explicit dependency array. The dependency tracking happens automatically: $derived and $effect detect which reactive values they read during execution and re-run only when those specific values change.

Migrating from Svelte 4

Existing Svelte 4 code using let and $: continues to work during a transition period, since Svelte 5’s compiler supports both models side by side in the same project (though not always mixed within a single component in ways that make sense). The practical migration path is incremental: new components can adopt runes immediately, while older components are converted file by file as they’re touched.

The biggest behavioral difference to watch for is $state’s deep reactivity: assigning to a nested property of a $state object (user.name = "new") triggers updates the same way a top-level reassignment does, which wasn’t reliably true under the old let-based model without extra $: wiring or object spreads.

Where this leaves Svelte relative to other frameworks

Runes bring Svelte’s mental model closer to Vue and React in the sense that reactive state is now an explicit, composable value rather than an implicit compiler behavior — while keeping Svelte’s compile-to-vanilla-JS approach, which is also central to how resumability and hydration strategies differ across frameworks more broadly. The net effect for developers coming from Svelte 4 is more explicit code with fewer surprises about where reactivity does or doesn’t apply, at the cost of a few extra characters per declaration.

The takeaway

Runes replace Svelte’s implicit let/$: reactivity with explicit primitives — $state, $derived, $effect, and $props — that work the same way in a component or a plain module, making reactive logic portable and composable in a way the compiler-inferred model never allowed. The compiler still strips runes away at build time into fine-grained update code, so the runtime cost stays low even as the authoring model becomes more explicit.

Takina Takina · · 4 min read

Remix vs Next.js: Which React Framework to Use

Remix and Next.js both extend React with routing and data loading, but differ sharply in rendering model, data fetching, and deployment targets.

#JavaScript #Frameworks #Web Development
Takina Takina · · 4 min read

Solid.js vs React: Two Models of Reactivity Compared

Solid.js uses fine-grained signals and no virtual DOM; React re-renders components and diffs. How the two reactivity models differ in practice.

#JavaScript #Frameworks #Web Development
Takina Takina · · 4 min read

Vue vs React: Which Framework Fits Your Project

Vue uses a template syntax with a reactive proxy system; React uses JSX with a virtual DOM. How the two frameworks differ and when to pick each.

#JavaScript #Frameworks #Web Development