Sharing State Across Components
Extracting $state into its own module so unrelated components can read and write the same value.
2 min de lectura
Props and callbacks (from the component communication lesson) handle direct parent-child relationships well. But sometimes two components need to share state without being in that relationship at all — a shopping cart count shown in a header, updated from a product page several levels away. Passing props down through every layer in between just to bridge that gap ("prop drilling") gets unwieldy fast.
State lives outside components too
$state isn't limited to a component's <script> block — it works in any .svelte.js (or .svelte.ts) module, and a plain JavaScript module is naturally shared: every file that imports it gets the same instance.
// cart.svelte.js
let items = $state([]);
export const cart = {
get items() {
return items;
},
add(product) {
items.push(product);
},
get count() {
return items.length;
},
};<!-- Header.svelte -->
<script>
import { cart } from './cart.svelte.js';
</script>
<p>Cart: {cart.count}</p><!-- ProductPage.svelte -->
<script>
import { cart } from './cart.svelte.js';
</script>
<button onclick={() => cart.add({ name: 'Widget' })}>
Add to cart
</button>Neither component knows about the other, yet both stay in sync — clicking "Add to cart" on the product page updates the count shown in the header immediately.
Why an object with getters, not a bare export
You might expect to just export let count = $state(0) and import count directly. That doesn't work the way you'd want: destructuring or importing a let binding copies its current value at import time in most module systems' mental model, and reassigning the exported variable from another file isn't something ES modules support. Wrapping the state in an object (or a class) sidesteps this — you're always reading a property off a live object, not a disconnected copy of a primitive.
// Counter.svelte.js
class Counter {
count = $state(0);
increment() {
this.count++;
}
}
export const counter = new Counter();A class with $state fields, as above, is a common alternative shape for the same idea — pick whichever reads more naturally for the shared state in question.
When to reach for this
Shared runes state is best for state that's genuinely global to part of your app — a cart, a logged-in user, a theme setting. For state that's local to one part of the component tree, plain props are still simpler to trace and should stay your default; shared state that's overused turns into the same "who changed this, and when?" debugging problem global variables always have.