Cross-Component State
Pattern 1: a shared module
Section titled “Pattern 1: a shared module”The simplest cross-component state is a reactive value exported from a .svelte.js module. Every importer shares one instance:
export const theme = $state({ mode: 'light' });
export function toggle() { theme.mode = theme.mode === 'light' ? 'dark' : 'light';}Any component imports theme and toggle and stays in sync. This is ideal for genuinely global, single-instance state: the color theme, a toast queue, a feature-flag set.
The SSR singleton trap
Section titled “The SSR singleton trap”There’s a catch that matters the moment you use SvelteKit (or any SSR). A module-level $state is a single instance shared by everything that imports it — including, on the server, every user request handled by the same process. Put a logged-in user in a module-level singleton and, under SSR, one request can see another user’s data.
Pattern 2: per-subtree instances with context
Section titled “Pattern 2: per-subtree instances with context”When you need state that is shared within a subtree but isolated per instance — and safe under SSR — combine a runed class/object with setContext / getContext. The parent creates the state and puts it in context; descendants read it. Each component instance gets its own, created during that request’s render.
<script> import { setContext } from 'svelte';
// created fresh per instance / per request — not a module singleton const cart = $state({ items: [] }); setContext('cart', cart);</script>
<slot /><script> import { getContext } from 'svelte'; const cart = getContext('cart'); // the same reactive object the parent set</script>
<button onclick={() => cart.items.push(item)}>add ({cart.items.length})</button>Because the state is created inside the component tree (not at module load), each render — and each SSR request — gets its own copy. A common idiom is a small factory (createCart()) that returns runed state, called in the parent and shared via context.
flowchart TB local["Just one component? local $state"] --> up["A few related components? lift $state to the parent, pass props"] up --> ctx["A subtree, per-instance / per-user? setContext + runed state"] ctx --> mod["Truly global, single instance? export $state from a .svelte.js module"] mod --> store["Need the subscribe contract? a store"]
The decision guide
Section titled “The decision guide”- Local —
$statein the component. Start here. - Lift — move
$stateto the closest common parent, pass down as props. For a handful of related components. - Context —
setContext/getContextwith runed state for a subtree that needs its own instance (and SSR safety). - Module — export
$statefrom a.svelte.jsmodule for truly global, single-instance state. - Store — when you need the subscribe contract (interop, external push sources).
Reach for the lowest level that solves your problem; escalate only when you must.