Stores

Sharing state across components with writable and readable stores and the $store syntax.

Why stores exist

Component props and local reactive let variables work well for state that belongs to one component or flows in a clear parent-to-child line. But some state genuinely needs to be shared across parts of the app that aren't in a direct parent-child relationship — a logged-in user, a shopping cart, a theme preference. Svelte's answer is the store: a plain, framework-agnostic object with a subscribe method that any component can read from and react to changes in, regardless of where that component sits in the component tree.

Writable stores

A writable store, created with writable() from svelte/store, holds a value that can be read, set, and updated from anywhere:

Javascript
// stores/counter.js
import { writable } from "svelte/store";

export const count = writable(0);
HTML
<!-- CounterDisplay.svelte -->
<script>
  import { count } from "./stores/counter.js";
</script>

<p>Count: {$count}</p>
HTML
<!-- CounterButtons.svelte -->
<script>
  import { count } from "./stores/counter.js";

  function increment() {
    count.update((n) => n + 1);
  }

  function reset() {
    count.set(0);
  }
</script>

<button on:click={increment}>+1</button>
<button on:click={reset}>Reset</button>

CounterDisplay and CounterButtons don't need to be related in the component tree at all — both simply import the same count store and interact with the one shared value it holds. .set(newValue) replaces the value outright; .update(fn) computes the new value from the current one, which is the safer choice whenever the update depends on what the value already is.

The $store auto-subscription syntax

Writing $count inside a .svelte file's markup or script — note the $ prefix — is Svelte's auto-subscription syntax: the compiler automatically subscribes to the store when the component mounts, unsubscribes when it's destroyed, and gives you the store's current unwrapped value directly, with no manual .subscribe() callback to write:

HTML
<script>
  import { count } from "./stores/counter.js";
</script>

<p>Count is: {$count}</p>

<button on:click={() => count.update((n) => n + 1)}>Increment</button>

Without this shorthand, you'd have to subscribe manually and remember to clean up, similar in spirit to a manual event listener:

Javascript
import { count } from "./stores/counter.js";

let currentCount;
const unsubscribe = count.subscribe((value) => {
  currentCount = value;
});

// later, when done:
unsubscribe();

The $ prefix is only special inside .svelte files (where the compiler can generate this subscription management automatically) — in a plain .js file, you always subscribe and unsubscribe manually as shown above.

A complete example: a shared theme store

Javascript
// stores/theme.js
import { writable } from "svelte/store";

export const theme = writable("light");

export function toggleTheme() {
  theme.update((current) => (current === "light" ? "dark" : "light"));
}
HTML
<!-- ThemeToggle.svelte -->
<script>
  import { theme, toggleTheme } from "./stores/theme.js";
</script>

<button on:click={toggleTheme}>
  Current theme: {$theme} (click to toggle)
</button>
HTML
<!-- App.svelte -->
<script>
  import { theme } from "./stores/theme.js";
  import ThemeToggle from "./ThemeToggle.svelte";
</script>

<main class={$theme}>
  <ThemeToggle />
  <p>The page background reflects the current theme.</p>
</main>

App.svelte and ThemeToggle.svelte both read $theme reactively — whichever component calls toggleTheme(), every other component subscribed to theme re-renders with the new value automatically, with no props being manually threaded through the component tree to make that happen.

Readable stores and derived stores

A readable store, created with readable(), exposes a value that only the store's own internal logic can update — useful for things like a value that comes from an external source (geolocation, a WebSocket, the current time), where consuming components should only ever read it, never set it directly:

Javascript
import { readable } from "svelte/store";

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

  return () => clearInterval(interval); // cleanup when the last subscriber unsubscribes
});

A derived store, created with derived(), computes its value from one or more other stores — Svelte's store-level equivalent of a computed():

Javascript
import { derived } from "svelte/store";
import { count } from "./stores/counter.js";

export const doubled = derived(count, ($count) => $count * 2);

Common mistakes

  • Forgetting the $ prefix and writing {count} instead of {$count} in a template — that renders the store object itself (or logs [object Object]), not its current value.
  • Subscribing to a store manually in a .js file (outside a .svelte component) and forgetting to call the returned unsubscribe() function, leaking the subscription.
  • Reaching for a store when local component state (let inside one .svelte file) would do — stores are for genuinely shared, cross-component state, not a default replacement for ordinary component-local reactivity.
  • Mutating an object or array held inside a store in place ($store.items.push(x)) instead of using .update() with a reassignment — the same reassignment-vs-mutation rule from component reactivity applies to store values too.