Reading Context from Ancestors
In the previous article, we learned how to provide context—how to broadcast data from an ancestor component so descendants can receive it. Now we turn to the other side of the equation: consuming context.
Providing context is straightforward: call setContext with a key and value, done. Consuming it has more nuance. What happens when context doesn’t exist? How do you handle optional versus required context? What about type safety? And there’s a subtle reactivity trap with destructuring that catches many developers off guard.
This article is intentionally thoroughYou don’t need to absorb everything on the first read — focus on the patterns and revisit as needed. This guide serves both as a learning resource and a reference you’ll return to.
This article covers everything you need to consume context confidently:
- The three consumer functions:
getContext,hasContext, andgetAllContexts - Handling missing context: fail-fast, fallbacks, and defensive patterns
- Building reusable utilities that standardize context access
- Type-safe consumption with
createContextand typed helpers - The destructuring trap that breaks reactivity (and how to avoid it)
The getContext Function
The getContext function is your primary tool for receiving context values. Let’s examine it thoroughly.
Function Signature
function getContext<T>(key: any): T The function takes a single parameter, the key that identifies which context you want, and returns the context value. The key must exactly match what was used in setContext.
Basic Usage
At its simplest:
<script>
import { getContext } from 'svelte'
const user = getContext('user')
</script>
<p>Welcome, {user.name}!</p> If an ancestor component called setContext('user', { name: 'Alice' }), then getContext('user') returns { name: 'Alice' }.
The Lookup Process
When you call getContext(key), Svelte performs a lookup that walks up the component tree:
Start at the current component’s parent — Context is set on components, but you can only access context from ancestors, not from yourself.
Check each ancestor — Svelte checks if that ancestor called
setContext(key, ...).Return on first match — As soon as an ancestor with that key is found, its value is returned.
Return
undefinedif not found — If no ancestor set that key, returned value isundefinedand not an error so be careful.
This process is visualized below:
App (setContext 'user' → Alice) ← Step 4: Found! Return Alice
└── Layout ← Step 3: No 'user' context
└── Dashboard ← Step 2: No 'user' context
└── UserBadge ← Step 1: getContext('user') called here
Looking for 'user'... IMPORTANT: This lookup happens only once during component initialization — context is structural, not reactive by design.
The “Nearest Wins” Rule
If multiple ancestors set the same key, you get the value from the nearest (most immediate) ancestor:
RootLayout -> (setContext 'theme' → 'light')
└── Dashboard
└── AdminPanel -> (setContext 'theme' → 'dark')
└── Settings
└── ThemeDisplay ← getContext('theme') returns 'dark' ThemeDisplay
↑ SettingsPanel (no theme)
↑ DashboardLayout ← FOUND: 'dark'
↑ RootLayout (ignored) The ThemeDisplay component in Settings page gets 'dark' because AdminPanel is closer than RootLayout. This enables powerful override patterns where inner components can customize context for their subtree without affecting the rest of the application,
What getContext Returns
The return value is exactly what was passed to setContext. No transformation, no copying, no proxy — just the same value.
For primitives (strings, numbers, booleans … ), you get the value as it was at the time setContext was called:
<!-- Provider -->
<script>
import { setContext } from 'svelte'
let count = 5
setContext('count', count) // Stores 5
count = 100 // Doesn't affect context
</script>
<!-- Consumer -->
<script>
import { getContext } from 'svelte'
const count = getContext('count') // Gets 5, not 100
</script> For objects (including arrays, functions, class instances), you get a reference to the same object:
<!-- Provider -->
<script>
import { setContext } from 'svelte'
const user = { name: 'Alice', score: 0 }
setContext('user', user)
// Later...
user.score = 100 // This IS visible to consumers
</script>
<!-- Consumer -->
<script>
import { getContext } from 'svelte'
const user = getContext('user')
console.log(user.score) // 100 (sees the modification)
</script> This reference sharing is the foundation of reactive context patterns — but it requires care. We’ll explore this in depth in the Making Context Reactive article.
Missing Context Returns - undefined
A critical behavior: getContext does not throw Error when the key isn’t found. It silently returns undefined:
<script>
import { getContext } from 'svelte'
const user = getContext('user') // undefined if no ancestor set 'user'
// This would crash!
console.log(user.name) // TypeError: Cannot read property 'name' of undefined
</script> Here’s what happens when context is missing:
┌──────────────────────────────────────────────────────────────────┐
│ Component Initialization │
│ │
│ const user = getContext('user') // No provider found │
│ ↓ │
│ Returns undefined ⚠️ (No error thrown!) │
│ ↓ │
│ Execution continues normally... │
└──────────────────────────────────────────────────────────────────┘
│
↓ Later, when code tries to use it...
┌──────────────────────────────────────────────────────────────────┐
│ Event Handler / Effect / Render │
│ │
│ function handleLogin() { │
│ user.login(credentials) ❌ TypeError: undefined.login │
│ } │
│ │
│ <h1>{user.name}</h1> ❌ TypeError: undefined.name │
└──────────────────────────────────────────────────────────────────┘
Problem: Error occurs far from root cause!
↓
Three Prevention Strategies:
┌─────────────────────┐ ┌─────────────────────┐ ┌──────────────────────┐
│ 1. FAIL FAST │ │ 2. FALLBACK │ │ 3. NULLABLE │
│ │ │ │ │ │
│ if (!hasContext()) │ │ const user = │ │ const user = │
│ throw Error │ │ hasContext() │ │ hasContext() │
│ │ │ ? getContext() │ │ ? getContext() │
│ ✅ Detect at init │ │ : defaultUser │ │ : null │
│ ✅ Clear message │ │ │ │ │
│ ✅ Can't be ignored │ │ ✅ Always usable │ │ if (user) { │
│ │ │ ✅ No crash │ │ // Has context │
│ Use: Required │ │ │ │ } else { │
│ context │ │ Use: Optional │ │ // No context │
│ │ │ enhancement │ │ } │
│ │ │ │ │ │
│ │ │ │ │ ✅ Explicit check │
│ │ │ │ │ ✅ Branch logic │
│ │ │ │ │ │
│ │ │ │ │ Use: Conditional │
│ │ │ │ │ features │
└─────────────────────┘ └─────────────────────┘ └──────────────────────┘ This behavior is intentional — it allows optional context patterns — but requires careful handling. The next section covers when and how to use each strategy.
The Timing Constraint: When getContext Can Be Called
Like setContext, getContext must be called during component initialization — the synchronous execution of your <script> block.
Component Lifecycle Timeline
─────────────────────────────────────────────────────────────────────
Phase 1: INITIALIZATION Phase 2: REACTIVE RUNTIME
┌─────────────────────────┐ ┌──────────────────────────┐
│ ✅ getContext ALLOWED │ │ ❌ getContext FORBIDDEN │
│ │ │ │
│ <script> │ │ • Event handlers │
│ const x = getContext()│ │ • $effect() callbacks │
│ │ │ • setTimeout/Promise │
│ function setup() { │ │ • After await │
│ const y = getContext│ │ │
│ } │ │ function onClick() { │
│ setup() // ✅ Called │ │ getContext() // ❌ │
│ immediately │ │ } │
│ </script> │ │ │
│ │ │ $effect(() => { │
│ All synchronous code │ │ getContext() // ❌ │
│ in <script> block runs │ │ }) │
└─────────────────────────┘ └──────────────────────────┘
│ │
│ Component mounted │ Reactive updates
↓ ↓
[Context captured] [Use captured values] Key insight: Capture context references early, use them throughout. If the context value contains $state properties, those remain reactive even though the lookup itself was one-time.
Valid Locations
the following are valid places to call getContext:
<script>
import { getContext } from 'svelte'
// ✅ Top-level in script block
const theme = getContext('theme')
// ✅ Inside synchronous function called immediately
function setupFromContext() {
const config = getContext('config')
return config.apiUrl
}
const apiUrl = setupFromContext() // Called during initialization
// ✅ Inside IIFE (Immediately Invoked Function Expression)
const user = (() => {
return getContext('user')
})()
// ✅ Inside synchronous conditional
let debug = false
if (import.meta.env.DEV) {
const devTools = getContext('devTools')
debug = devTools?.enabled ?? false
}
</script> Invalid Locations
do not call getContext in these places:
<script>
import { getContext } from 'svelte'
// ❌ Inside event handler
function handleClick() {
const user = getContext('user') // Error!
}
// ❌ Inside $effect
$effect(() => {
const theme = getContext('theme') // Error!
})
// ❌ Inside $derived (which runs reactively)
// Note: $derived runs during initialization, but subsequent runs are reactive
// This is a subtle edge case—generally avoid getContext in $derived
// ❌ Inside setTimeout
setTimeout(() => {
const config = getContext('config') // Error!
}, 0)
// ❌ Inside Promise callbacks
fetch('/api').then(() => {
const api = getContext('api') // Error!
})
// ❌ After await (which makes the rest async)
async function setup() {
const data = await fetch('/api').then((r) => r.json())
const config = getContext('config') // Error!
}
setup()
</script> The Error Message
If you call getContext outside initialization, Svelte throws:
Error: `getContext(...)` can only be called during component initialization Why This Restriction Exists
This restriction mirrors the one on setContext, and for related reasons:
Context is structural — It’s part of how the component tree is organized, established at mount time.
Predictability — You can rely on context being available (or not) from the moment the component initializes. It won’t suddenly appear or disappear.
Performance — Svelte doesn’t need to track context access reactively; it’s a one-time lookup during initialization.
The Workaround: Capture During Initialization
The solution is simple: capture context values during initialization, then use them wherever you need:
<script>
import { getContext } from 'svelte'
// Capture during initialization
const auth = getContext('auth')
const theme = getContext('theme')
const toast = getContext('toast')
// Now use freely in event handlers
function handleLogin() {
auth
.login(credentials)
.then(() => {
toast.success('Welcome back!')
})
.catch((err) => {
toast.error(err.message)
})
}
// And in effects
$effect(() => {
document.body.setAttribute('data-theme', theme.current)
})
// And in derived values
let greeting = $derived(`Hello, ${auth.user?.name ?? 'Guest'}!`)
</script>
<button onclick={handleLogin}>Login</button><p>{greeting}</p> The captured reference remains valid for the component’s lifetime. If the context value is an object with reactive properties (using $state), those reactive properties will update normally.
Checking Context with hasContext
Before accessing context, you might want to check if it exists. The hasContext function does exactly this.
Function Signature
function hasContext(key: any): boolean Returns true if any ancestor set this context key, false otherwise.
Basic Usage
<script>
import { getContext, hasContext } from 'svelte'
// Check before accessing
if (hasContext('user')) {
const user = getContext('user')
console.log('User:', user.name)
} else {
console.log('No user context available')
}
</script> Common Pattern: Check and Provide Default
<script>
import { getContext, hasContext } from 'svelte'
// Get context with fallback
const theme = hasContext('theme')
? getContext('theme')
: { mode: 'light', accentColor: '#007bff' }
</script> This pattern is so common that you might want to create a utility function (we’ll do that later).
When to Use hasContext
The decision of whether to check for context depends on your use case:
| Scenario | Approach | Reasoning |
|---|---|---|
| Context is required | Don’t check; let it fail | Missing required context is a bug; fail fast and loudly |
| Context is optional with sensible default | Check with hasContext, provide default | Component should work standalone |
| Different behavior with/without context | Check and branch | Some features only available in certain contexts |
| Library/reusable component | Usually check | Components might be used outside expected providers |
| Internal application component | Often don’t check | You control the environment |
hasContext Doesn’t Consume Context
An important detail: hasContext only checks existence—it doesn’t retrieve or “consume” the context. You can call it multiple times without side effects:
<script>
import { hasContext, getContext } from 'svelte'
// These are independent checks
const hasTheme = hasContext('theme')
const hasUser = hasContext('user')
const hasFeatureFlags = hasContext('features')
// Now conditionally get what we need
const theme = hasTheme ? getContext('theme') : defaultTheme
const user = hasUser ? getContext('user') : null
</script> Inspecting All Context with getAllContexts
The getAllContexts function returns the complete context map available at the current position in the tree.
Function Signature
function getAllContexts<T extends Map<any, any> = Map<any, any>>(): T Returns a JavaScript Map containing all context key-value pairs accessible to this component.
Basic Usage
<script>
import { getAllContexts } from 'svelte'
const allContexts = getAllContexts()
console.log(allContexts)
// Map(3) {
// 'theme' => { mode: 'dark', ... },
// 'user' => { name: 'Alice', ... },
// Symbol('auth') => { ... }
// }
// Use Map methods
console.log(allContexts.has('theme')) // true
console.log(allContexts.get('user')) // { name: 'Alice', ... }
console.log(allContexts.size) // 3
</script> 1: Debugging
When something isn’t working, it’s helpful to see what context is actually available:
<script>
import { getAllContexts } from 'svelte'
// Development-only context inspection
if (import.meta.env.DEV) {
const ctx = getAllContexts()
console.group('🔍 Available Context')
for (const [key, value] of ctx) {
console.log(`${String(key)}:`, value)
}
console.groupEnd()
}
</script> You might create a reusable debug component:
<!-- ContextDebugger.svelte -->
<script>
import { getAllContexts } from 'svelte'
let { show = false } = $props()
const allContexts = getAllContexts()
const entries = [...allContexts.entries()]
</script>
{#if show && import.meta.env.DEV}
<details class="context-debugger">
<summary>Context ({entries.length} keys)</summary>
<ul>
{#each entries as [key, value]}
<li>
<strong>{String(key)}</strong>
<pre>{JSON.stringify(value, null, 2)}</pre>
</li>
{/each}
</ul>
</details>
{/if}
<style>
.context-debugger {
position: fixed;
bottom: 1rem;
right: 1rem;
background: #1e1e1e;
color: #d4d4d4;
padding: 0.5rem;
border-radius: 4px;
font-size: 12px;
max-width: 400px;
max-height: 300px;
overflow: auto;
z-index: 9999;
}
pre {
margin: 0;
white-space: pre-wrap;
word-break: break-all;
}
</style> 2: Forwarding Context to Dynamic Components
When you dynamically mount a component outside the normal tree (e.g., a modal rendered in a portal), you may want it to inherit the current context:
<script>
import { mount, getAllContexts } from 'svelte'
import Modal from './Modal.svelte'
// Capture context that should be forwarded
const inheritedContext = getAllContexts()
let modalInstance = null
function openModal(content) {
// Mount modal at document.body but with inherited context
modalInstance = mount(Modal, {
target: document.body,
props: { content },
context: inheritedContext // Forward context!
})
}
function closeModal() {
if (modalInstance) {
modalInstance.$destroy()
modalInstance = null
}
}
</script> Without forwarding context, the modal would have no context at all (since document.body has no Svelte ancestor with context).
3: Creating Context-Forwarding Wrappers
Sometimes you need to wrap a component while preserving all its context access:
<!-- ContextPreservingWrapper.svelte -->
<script>
import { getAllContexts, setContext } from 'svelte'
let { children, additionalContext = {} } = $props()
// Get all inherited context
const inherited = getAllContexts()
// Re-provide everything from ancestors
for (const [key, value] of inherited) {
setContext(key, value)
}
// Add any additional context
for (const [key, value] of Object.entries(additionalContext)) {
setContext(key, value)
}
</script>
<div class="wrapper">
{@render children()}
</div> Timing Note
Like the other context functions, getAllContexts must be called during component initialization.
Handling Missing Context: Strategies and Patterns
Missing context is one of the most common sources of runtime errors in context-based architectures. A component expects getContext('user') to return a user object, but instead gets undefined—and suddenly you’re debugging a cryptic “Cannot read property ‘name’ of undefined” error three components away from the actual problem.
The key insight is that different situations call for different strategies. Sometimes missing context is a bug that should crash loudly. Other times it’s expected and your component should gracefully degrade. Let’s explore when to use each approach.
Strategy 1: Fail Fast for Required Context
When context is genuinely required—meaning your component cannot function without it—the best approach is to fail immediately with a clear, actionable error message:
<script>
import { getContext, hasContext } from 'svelte'
if (!hasContext('auth')) {
throw new Error(
'AuthContext not found. ' +
'Wrap this component in a layout that provides auth context. ' +
'See: src/routes/+layout.svelte'
)
}
const auth = getContext('auth')
</script> Why fail fast?
- Immediate feedback: You discover the problem during development, not in production when a user clicks a button
- Clear diagnosis: The error message tells developers exactly what’s wrong and where to look
- Prevents cascading failures: Without this check, you’d get a confusing error like
TypeError: Cannot read property 'login' of undefinedsomewhere deep in an event handler
Use this strategy for contexts that are architectural requirements—things like authentication, routing, or app-wide configuration.
Strategy 2: Graceful Fallback for Optional Context
Some context enhances a component but isn’t essential. A Button component might look better when it respects theme context, but it should still render a perfectly functional button without it:
<script>
import { getContext, hasContext } from 'svelte'
let { children, onclick } = $props()
// Theme is optional—component works without it
const defaultTheme = {
primaryColor: '#007bff',
borderRadius: '4px'
}
const theme = hasContext('theme') ? getContext('theme') : defaultTheme
</script>
<button
{onclick}
style="
background: {theme.primaryColor};
border-radius: {theme.borderRadius};
"
>
{@render children()}
</button> This pattern makes components portable. You can drop them into any project—whether it has your theme system or not—and they just work.
Strategy 3: Explicit Null for Conditional Features
Sometimes you need to know whether context was provided versus using a default. For example, you might show different UI for logged-in versus anonymous users:
<script>
import { getContext, hasContext } from 'svelte'
// Explicitly null when not available (not a default user object)
const user = hasContext('user') ? getContext('user') : null
</script>
{#if user}
<nav>
<span>Welcome, {user.name}</span>
<a href="/dashboard">Dashboard</a>
<button onclick={() => auth.logout()}>Logout</button>
</nav>
{:else}
<nav>
<a href="/login">Sign In</a>
<a href="/register">Create Account</a>
</nav>
{/if} Using null (rather than a fake default user) makes the distinction crystal clear in your code.
Strategy 4: Optional Chaining for Quick Access
For simple cases where you just need safe property access, optional chaining is concise:
<script>
import { getContext } from 'svelte'
const user = getContext('user')
const analytics = getContext('analytics')
</script>
<!-- Safe even if user is undefined -->
<p>Hello, {user?.name ?? 'Guest'}!</p>
<!-- Conditionally call methods -->
<button onclick={() => analytics?.track('button_clicked')}> Click me </button> This works well for leaf components that consume context passively. However, avoid overusing it—if you find yourself writing user?. everywhere, consider using Strategy 2 or 3 at the top of your script instead.
Choosing the Right Strategy
| Scenario | Strategy | Why |
|---|---|---|
| Component cannot function without context | Fail Fast | Catch config errors immediately |
| Context enhances but isn’t required | Graceful Fallback | Component stays portable |
| Different UI for “has context” vs “doesn’t” | Explicit Null | Clear conditional logic |
| One-off property access | Optional Chaining | Concise for simple cases |
| Library/reusable component | Graceful Fallback | Works in any environment |
| Internal app component with guaranteed provider | Fail Fast | Documents the requirement |
Building Reusable Context Utilities
If you find yourself repeating the same context-handling patterns across many components, it’s time to extract them into utilities. This not only reduces boilerplate but also standardizes how your team handles context throughout the application.
A Practical Utility Module
Here’s a set of utilities that cover the most common patterns:
// src/lib/context/utils.ts
import { getContext, hasContext } from 'svelte'
/**
* Gets context or returns a fallback value.
* Use for optional context with sensible defaults.
*
* @example
* const theme = getContextOr('theme', { mode: 'light' })
*/
export function getContextOr<T>(key: any, fallback: T): T {
return hasContext(key) ? getContext(key) : fallback
}
/**
* Gets context or returns null.
* Use when you need to distinguish "no context" from "has context".
*
* @example
* const user = getContextOrNull('user')
* if (user) { ... }
*/
export function getContextOrNull<T>(key: any): T | null {
return hasContext(key) ? getContext(key) : null
}
/**
* Gets context or throws a descriptive error.
* Use for required context that must be provided by an ancestor.
*
* @example
* const auth = requireContext('auth', 'Authentication')
*/
export function requireContext<T>(key: any, contextName: string): T {
if (!hasContext(key)) {
throw new Error(
`${contextName} context is required but was not found. ` +
`Ensure this component is rendered within a provider that sets '${String(key)}' context.`
)
}
return getContext(key)
} Using the Utilities
With these utilities, your components become cleaner and more consistent:
<script>
import { requireContext, getContextOr, getContextOrNull } from '$lib/context/utils'
// Required: throws descriptive error if missing
const auth = requireContext('auth', 'Authentication')
// Optional with default: always returns a usable value
const theme = getContextOr('theme', { mode: 'light', accent: '#007bff' })
// Optional nullable: returns null if missing
const analytics = getContextOrNull('analytics')
function trackClick() {
// Only track if analytics is available
analytics?.track('button_clicked')
}
</script> Creating Typed Context Accessors
For even better developer experience, create typed accessors for each context in your app:
// src/lib/context/auth.ts
import { getContext, hasContext, setContext } from 'svelte'
export interface AuthContext {
readonly user: User | null
readonly isAuthenticated: boolean
login(credentials: Credentials): Promise<void>
logout(): Promise<void>
}
const AUTH_KEY = Symbol('auth')
export function setAuthContext(value: AuthContext) {
return setContext(AUTH_KEY, value)
}
export function getAuthContext(): AuthContext {
if (!hasContext(AUTH_KEY)) {
throw new Error(
'Auth context not found. ' +
'Ensure this component is rendered within src/routes/+layout.svelte'
)
}
return getContext(AUTH_KEY)
}
export function getAuthContextOrNull(): AuthContext | null {
return hasContext(AUTH_KEY) ? getContext(AUTH_KEY) : null
} Now consuming auth context is fully typed and self-documenting:
<script lang="ts">
import { getAuthContext } from '$lib/context/auth'
const auth = getAuthContext()
// Full TypeScript support
auth.user?.name // ✅ string | undefined
auth.isAuthenticated // ✅ boolean
auth.login(creds) // ✅ Promise<void>
</script> This pattern scales well—create one file per context domain (auth.ts, theme.ts, toast.ts) and your team always knows exactly how to provide and consume each context.
Type-Safe Consumption
TypeScript integration makes consuming context safer and more ergonomic.
Basic Type Assertions
With raw getContext, you need to assert types:
<script lang="ts">
import { getContext } from 'svelte'
interface User {
id: string
name: string
email: string
}
// Type assertion needed
const user = getContext('user') as User
// Or with generic
const user2 = getContext<User>('user')
</script> This works but has drawbacks:
- The assertion could be wrong (no runtime verification)
- You need to import/define the type everywhere you consume
Using createContext (Recommended)
With createContext (Svelte 5.40+), types are inferred automatically:
// lib/context/user.ts
import { createContext } from 'svelte'
export interface User {
id: string
name: string
email: string
role: 'admin' | 'user' | 'guest'
}
export const [getUser, setUser] = createContext<User>() <!-- Consumer.svelte -->
<script lang="ts">
import { getUser } from '$lib/context/user'
// Fully typed! No assertion needed.
const user = getUser()
// TypeScript knows everything
user.name // ✅ string
user.email // ✅ string
user.role // ✅ 'admin' | 'user' | 'guest'
user.foo // ❌ Property 'foo' does not exist
</script> createContext Throws on Missing
Unlike getContext, the getter from createContext throws when context is missing:
<script>
import { getUser } from '$lib/context/user'
// If no ancestor called setUser(), this throws immediately:
// "Context was not provided"
const user = getUser()
</script> This is a feature, not a bug—it catches configuration errors during development rather than causing mysterious undefined errors later.
createContext with Default Values
You can provide a default value that’s used when no provider sets context:
// lib/context/config.ts
import { createContext } from 'svelte'
interface Config {
apiUrl: string
timeout: number
debug: boolean
}
export const [getConfig, setConfig] = createContext<Config>({
apiUrl: '/api',
timeout: 5000,
debug: false
}) Now getConfig() returns the default if no ancestor provided config, instead of throwing.
Consuming Multiple Contexts
Real-world components often need data from several contexts—theme settings, user information, feature flags, toast notifications. Let’s explore patterns for managing this cleanly.
Direct Approach: Multiple getContext Calls
The straightforward approach works well for most components:
<script>
import { getContext } from 'svelte'
const theme = getContext('theme')
const user = getContext('user')
const locale = getContext('locale')
const toast = getContext('toast')
</script> This is explicit, easy to understand, and works perfectly for components that need a handful of contexts. Don’t over-engineer it.
Consolidated Hook for Common Context Sets
If many components need the same combination of contexts, extract a hook:
// src/lib/hooks/useAppContext.ts
import { getContext, hasContext } from 'svelte'
export function useAppContext() {
return {
user: getContext('user'),
theme: hasContext('theme') ? getContext('theme') : { mode: 'light' },
locale: getContext('locale'),
toast: getContext('toast')
}
} <script>
import { useAppContext } from '$lib/hooks/useAppContext'
const { user, theme, locale, toast } = useAppContext()
</script> Remember: This function must be called during component initialization—you can’t call it inside an event handler.
Domain-Specific Hooks (Recommended for Large Apps)
Rather than one monolithic hook, create focused hooks for each domain:
// src/lib/hooks/useAuth.ts
import { requireContext } from '$lib/context/utils'
export function useAuth() {
return requireContext('auth', 'Authentication')
} // src/lib/hooks/useTheme.ts
import { getContextOr } from '$lib/context/utils'
const defaultTheme = { mode: 'light', accent: '#007bff' }
export function useTheme() {
return getContextOr('theme', defaultTheme)
} // src/lib/hooks/usePermissions.ts
import { getContextOr } from '$lib/context/utils'
export function usePermissions() {
const permissions = getContextOr('permissions', [])
return {
list: permissions,
has: (perm: string) => permissions.includes(perm),
hasAll: (...perms: string[]) => perms.every((p) => permissions.includes(p)),
hasAny: (...perms: string[]) => perms.some((p) => permissions.includes(p))
}
} Usage is clean and self-documenting:
<script>
import { useAuth } from '$lib/hooks/useAuth'
import { useTheme } from '$lib/hooks/useTheme'
import { usePermissions } from '$lib/hooks/usePermissions'
const auth = useAuth()
const theme = useTheme()
const { has: hasPermission } = usePermissions()
</script>
{#if hasPermission('admin')}
<AdminPanel />
{/if} This pattern scales well because each hook:
- Encapsulates its own error handling
- Provides sensible defaults
- Can add computed properties (like
hasPermission) - Is independently testable
The Destructuring Trap: A Common Reactivity Pitfall
There’s a subtle but important gotcha when working with reactive context that trips up many developers. Understanding it will save you hours of debugging.
The Problem: Lost Reactivity
Consider this innocent-looking code:
<script>
import { getContext } from 'svelte'
const settings = getContext('settings')
// settings = { theme: 'light', fontSize: 16, language: 'en' }
// Destructuring seems convenient...
const { theme, fontSize, language } = settings
</script>
<div class={theme}>
<p style="font-size: {fontSize}px">Language: {language}</p>
</div> Now imagine the provider updates settings.theme to 'dark'. Your component won’t update. It still shows 'light'.
Why This Happens
When you destructure, JavaScript extracts the values at that moment and assigns them to new variables:
const { theme } = settings
// Is equivalent to:
const theme = settings.theme // Copies 'light' into a new variable The theme variable now holds the string 'light' independently. There’s no ongoing connection to themeSettings.theme. When the provider changes themeSettings.theme to 'dark', your theme variable is unaffected—it’s still 'light'.
Solution 1: Access Properties Directly (Simplest)
The easiest fix is to skip destructuring and access properties on the object:
<script>
import { getContext } from 'svelte'
const settings = getContext('settings')
</script>
<!-- Access properties directly—this WILL update -->
<div class={settings.theme}>
<p style="font-size: {settings.fontSize}px">
Language: {settings.language}
</p>
</div> Each time Svelte renders, it reads the current value of settings.theme. If the provider changed it, you see the new value.
Solution 2: Reactive Destructuring with $derived
If you want the convenience of short variable names, use $derived to create reactive bindings:
<script>
import { getContext } from 'svelte'
const settings = getContext('settings')
// These re-evaluate whenever settings changes
let theme = $derived(settings.theme)
let fontSize = $derived(settings.fontSize)
let language = $derived(settings.language)
</script>
<!-- Now these update automatically -->
<div class={theme}>
<p style="font-size: {fontSize}px">Language: {language}</p>
</div> Each $derived creates a reactive binding that re-computes whenever its dependencies change.
Solution 3: Template-Level Destructuring with {@const}
For one-off use in a specific template block, you can destructure right where you need it:
<script>
import { getContext } from 'svelte'
const settings = getContext('settings')
</script>
{@const { theme, fontSize, language } = settings}
<div class={theme}>
<p style="font-size: {fontSize}px">Language: {language}</p>
</div> The {@const} declaration runs on every render, so it always captures current values. This is useful when you need destructuring within a loop or conditional block.
When Does This Matter?
This trap only affects you when:
- The context value is reactive (contains
$stateinternally) - You destructure primitive values (strings, numbers, booleans)
- The provider actually changes those values
If your context is read-only configuration that never changes, destructuring is perfectly fine. But when in doubt, access properties directly—it’s always safe.
We’ll explore reactivity patterns in much greater depth in the Making Context Reactive article.
Putting It All Together: A Real-World Example
Let’s apply everything we’ve learned to build a Card component that demonstrates robust context consumption patterns.
The Requirements
Our Card component should:
- Adapt to theme context when available
- Work standalone with sensible defaults (portability)
- Support variants that derive colors from the theme
- Be fully typed for great developer experience
The Implementation
<!-- src/lib/components/Card.svelte -->
<script lang="ts">
import { getContext, hasContext } from 'svelte'
// --- Props ---
interface Props {
title?: string
subtitle?: string
variant?: 'default' | 'primary' | 'muted'
children: import('svelte').Snippet
}
let { title, subtitle, variant = 'default', children }: Props = $props()
// --- Theme Context (optional with fallback) ---
interface Theme {
mode: 'light' | 'dark'
colors: {
primary: string
background: string
text: string
muted: string
border: string
}
borderRadius: string
shadow: string
}
const defaultTheme: Theme = {
mode: 'light',
colors: {
primary: '#007bff',
background: '#ffffff',
text: '#1a1a1a',
muted: '#f5f5f5',
border: '#e0e0e0'
},
borderRadius: '8px',
shadow: '0 2px 8px rgba(0, 0, 0, 0.1)'
}
// Strategy 2: Graceful fallback for optional context
const theme: Theme = hasContext('theme') ? getContext('theme') : defaultTheme
// --- Derived Styling ---
// Use $derived to react to theme changes
let backgroundColor = $derived(
{
default: theme.colors.background,
primary: theme.colors.primary,
muted: theme.colors.muted
}[variant]
)
let textColor = $derived(variant === 'primary' ? '#ffffff' : theme.colors.text)
</script>
<article
class="card"
class:dark={theme.mode === 'dark'}
style="
--card-bg: {backgroundColor};
--card-text: {textColor};
--card-border: {theme.colors.border};
--card-radius: {theme.borderRadius};
--card-shadow: {theme.shadow};
"
>
{#if title}
<header class="card-header">
<h3 class="card-title">{title}</h3>
{#if subtitle}
<p class="card-subtitle">{subtitle}</p>
{/if}
</header>
{/if}
<div class="card-body">
{@render children()}
</div>
</article>
<style>
.card {
background: var(--card-bg);
color: var(--card-text);
border: 1px solid var(--card-border);
border-radius: var(--card-radius);
box-shadow: var(--card-shadow);
overflow: hidden;
}
.card.dark {
border-color: rgba(255, 255, 255, 0.1);
}
.card-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--card-border);
}
.card-title {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
}
.card-subtitle {
margin: 0.25rem 0 0;
opacity: 0.7;
font-size: 0.875rem;
}
.card-body {
padding: 1.5rem;
}
</style> What Makes This Robust
Works anywhere: The default theme ensures the card renders beautifully even without a theme provider
Adapts when context exists: When used within a themed layout, it automatically picks up the app’s colors and styling
Reactive to changes: Using
$derivedfor variant colors means the card updates if the theme toggles between light/dark modeFully typed: TypeScript catches mismatches between expected and provided theme structures
CSS custom properties: The bridge between script and styles keeps the component maintainable
Usage Examples
<!-- Works standalone -->
<div class="grid">
<!-- Works standalone -->
<Card title="Hello World">
<p>This card uses default styling.</p>
</Card>
<!-- Adapts to theme context -->
<Card title="Themed Card" variant="primary">
<p>This card uses your app's primary color.</p>
</Card>
<!-- Inside a dark-themed section -->
<Card title="Dark Mode" subtitle="Automatically adapts">
<p>Border and shadow adjust for dark backgrounds.</p>
</Card>
</div>
<style>
.grid {
display: grid;
gap: 1.5rem;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
margin-top: 2rem;
}
</style> Conclusion
Consuming context is the counterpart to providing it, and understanding both sides completes your mental model of how data flows through Svelte’s component tree. While providing context is about broadcasting availability, consuming context is about accessing that availability reliably and safely.
The patterns in this article—from basic getContext calls to sophisticated typed utilities—all address the same fundamental challenge: how do you access data that might or might not exist, in a way that’s type-safe, maintainable, and debuggable? The answer depends on your use case: fail fast for required context, graceful fallbacks for optional context, and explicit null checks when you need to distinguish between the two.
The destructuring trap deserves special emphasis because it catches so many developers off guard. The mental model to remember is simple: primitives are copied, references are shared. When you destructure a primitive from a reactive context object, you get a snapshot that never updates. When you access it through the object (context.theme instead of just theme), you get the current value every time.
With both providing and consuming context mastered, you’re equipped to build applications where data flows cleanly through your component tree—no prop drilling, no coupling through intermediates, just clear declarations of what’s available and what’s needed.
Key Takeaways
Consuming context effectively comes down to understanding a few key principles:
The Core Functions
getContext(key)retrieves context from the nearest ancestor. Returnsundefinedif not found—not an error.hasContext(key)checks existence without retrieving. Use this for optional context patterns.getAllContexts()returns everything as a Map. Great for debugging and context forwarding.
The Timing Rule
All context functions must run during component initialization. Capture what you need at the top of your script, then use those values anywhere.
Handling Missing Context
| Situation | Strategy |
|---|---|
| Component can’t work without it | Fail fast with descriptive error |
| Nice-to-have enhancement | Provide sensible default |
| Need to distinguish “missing” from “present” | Return explicit null |
| Quick one-off access | Optional chaining (context?.value) |
The Destructuring Trap
Destructuring breaks reactivity for primitive values. Either access properties directly on the context object, or use $derived to create reactive bindings.
Type Safety
Use createContext (Svelte 5.40+) for automatic typing and error-on-missing behavior, or create typed utility functions to standardize context access across your app.
With these patterns mastered, you’ll consume context safely and effectively throughout your Svelte applications.
What’s Next
Explore how context behaves in complex component hierarchies in Context Scope and Flow, where you’ll master the “closest wins” rule and context override patterns.
See Also
Official Documentation
- getContext API — API reference
- hasContext API — API reference
- getAllContexts API — API reference
Related Articles
- Providing Context — Setting up context
- Making Context Reactive — Reactive context patterns
- $derived Rune — Reactive derivations