State in Modules
React developers share state across the component tree with Context — createContext, a Provider wrapping part of the tree, and useContext in every consumer. It works, but it requires ceremony: the Provider must wrap all consumers, and adding a new piece of shared state means updating the Provider.
Svelte 5 introduces a simpler model: .svelte.ts modules. You declare $state and $derived at module scope in a .svelte.ts (or .svelte.js) file, export those variables, and import them in any component. The reactivity just works — every component that reads the variable re-renders when it changes. No Provider, no context object, no useContext call.
React Context vs .svelte.ts module
Section titled “React Context vs .svelte.ts module”// userStore.tsimport { createContext, useContext, useState } from 'react';
const UserContext = createContext(null);
export function UserProvider({ children }) { const [user, setUser] = useState(null); return ( <UserContext.Provider value={{ user, setUser }}> {children} </UserContext.Provider> );}
export function useUser() { return useContext(UserContext);}
// App.tsx — must wrap consumers<UserProvider> <Header /> <Main /></UserProvider>
// Header.tsxconst { user } = useUser();// user.svelte.tsexport let user = $state(null);export function setUser(next) { user = next; }
// Header.svelte — just import, no Provider needed<script> import { user, setUser } from './user.svelte.ts';</script>
<p>Hello, {user?.name ?? 'Guest'}</p><button onclick={() => setUser({ name: 'Ada' })}>Log in</button>
// Main.svelte — same import, same reactive state<script> import { user } from './user.svelte.ts';</script>
{#if user} <p>Welcome back, {user.name}!</p>{/if}Any component that imports user from user.svelte.ts automatically re-renders when user changes, because the Svelte compiler treats $state variables as reactive even at module scope.
Try it: inline analog
Section titled “Try it: inline analog”.svelte.ts modules require multiple files, which the playground can’t show. Below is a self-contained demo that uses the same reactive pattern — $state and $derived declared at the top of the script block, shared across simulated “sections” of the UI.
In a real project you would move the state declarations to a .svelte.ts file and import them.