Svelte 5 prefers Runes, but stores still work. Here's how to choose and migrate.
Comparison
| Feature | Stores | Runes |
|---|---|---|
| API | subscribe, set, update | Direct property access |
| Syntax | $store prefix | No special syntax |
| File location | Any .js file | .svelte.js files only |
| Learning curve | Higher | Lower |
| TypeScript | Good | Better |
| Library support | Wide | Growing |
Migration: writable to $state
Store way:
javascriptimport { writable } from 'svelte/store'; export const count = writable(0); // Usage in component $count++;
Rune way:
javascript// count.svelte.js export const count = $state({ value: 0 }); // Usage in component count.value++;
Migration: derived to $derived
Store way:
javascriptimport { writable, derived } from 'svelte/store'; const count = writable(0); const doubled = derived(count, $c => $c * 2);
Rune way:
javascript// counter.svelte.js let count = $state(0); let doubled = $derived(count * 2); export { count, doubled };
When Stores Still Make Sense
- Existing codebase: Don't rewrite working code just because
- Third-party libraries: Many still use stores
- Standard
.jsfiles: Runes require.svelte.js - Complex subscriptions: Custom store logic
Custom Stores (Still Useful)
javascriptimport { writable } from 'svelte/store'; function createPersisted(key, initial) { const stored = localStorage.getItem(key); const value = stored ? JSON.parse(stored) : initial; const { subscribe, set, update } = writable(value); return { subscribe, set: (v) => { localStorage.setItem(key, JSON.stringify(v)); set(v); }, update: (fn) => { update(v => { const newValue = fn(v); localStorage.setItem(key, JSON.stringify(newValue)); return newValue; }); } }; } export const preferences = createPersisted('prefs', { theme: 'light' });
š Stores documentation