Svelte 5 Runes: Component State Without Stores
Use $state, $derived, and $effect for component-local reactive state in Svelte 5, and keep stores for cross-component state.
22 Mar 2026, 16:32 UTC

Start with the component, not the store
You open a Svelte component to add a toggle, a counter, or a form field. In Svelte 4, the default was a top-level let variable or a store imported from another file. In Svelte 5, the compiler treats $state, $derived, and $effect as the supported primitives for component-local reactive state. If you reach for a store first, you add subscriptions and boilerplate for state that never leaves the component. If you use a rune outside a compiled .svelte file, it fails.
The practical rule: use runes for state owned by one component, and keep Svelte stores for state shared across components.
$state declares mutable, fine-grained state
$state creates reactive state you can mutate directly. Svelte tracks which properties are read while rendering the template, so only the parts of the DOM that depend on a changed value update. This is fine-grained reactivity: the compiler does not re-run the whole component when one property changes.
count += 1}>
Clicked {count} times
Clicking the button updates count and the DOM without manual subscriptions or a setState call.
$derived computes values lazily
$derived creates a value that updates when its dependencies change. It is lazy by default: it does not recompute until something reads it.
Doubled: {doubled}
$effect runs after the DOM updates
$effect runs side effects after rendering and tracks reactive reads. Use it to sync Svelte state with an external system, such as the page title. Do not mutate state inside an effect without a guard, or you can create an update loop.
Worked example: a live character counter
This component combines all three runes. The input is bound to $state, the length is $derived, and the page title is synced in $effect.
{length} characters
Typing in the input updates text, which recomputes length, which updates the paragraph and the effect. No store subscription is required.
Limitations and how to verify
- Runes are processed only inside
.sveltecomponents compiled with Svelte 5. They are unavailable in Svelte 4 and in plain JavaScript files. - Declare runes at the top level of a component or inside functions marked with
$effector$derived. Using them conditionally or inside loops breaks reactivity. $effectruns after rendering. Reading the DOM inside it is safe, but mutating state inside it can loop unless guarded.
To verify a project is ready for runes, check that the svelte dependency in package.json is a 5.x release. Create a scratch project with the Svelte 5 template, add a component like the example above, mutate state in an event handler, and confirm the derived value and DOM update without manual subscriptions.
Command example
# Run in the terminal at your project root npm create svelte@latest my-app # Select the Svelte 5 template when prompted
Required permissions: ordinary file access for the project folder. Placeholder: replace my-app with your directory name. Expected check: the installer creates the folder and package.json lists Svelte 5. Risk: reusing an existing directory name may overwrite files.
Rollback for a scratch project: delete the folder you created, for example rm -rf my-app.
Closing
Svelte 5 runes remove the boilerplate of stores for component-local state, but they are not a replacement for every store. Start with $state inside the component, derive what you need with $derived, and reach for $effect only when you must sync with something outside Svelte.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.