The Wall Every Svelte Developer Hits
Every Svelte developer eventually hits a wall. You have data in one component—maybe user information, theme settings, or application configuration—and you need that same data in another component buried deep within your component tree. The straightforward solution of passing the data as props through every intermediate component technically works, but something feels fundamentally wrong about it. You’re writing repetitive code that serves no purpose other than forwarding data through components that don’t even care about it.
This frustrating pattern has a name in the frontend development world: prop drilling. And Svelte’s Context API exists specifically to solve it.
Context in 60 Seconds
The Context API does one thing: it lets a parent component broadcast data that any descendant can receive directly, no matter how deeply nested.
Component A sets a context value. Every descendant (B, C, D, E, F, G) can access it directly with getContext().
When the intermediate components (B and C) don’t need to know the data exists — they don’t receive props and don’t forward anything. D and G can simply reach up and grab what they need using getContext().
Here’s what that looks like in code:
<!-- Provider.svelte (Component A) -->
<script>
import { setContext } from 'svelte'
import Intermediate from './Intermediate.svelte'
setContext('user', { name: 'Alice', role: 'admin' })
</script>
<Intermediate /> <!-- Intermediate.svelte (Component B) -->
<script>
import Consumer from './Consumer.svelte'
// No props! This component doesn't know about 'user'
</script>
<Consumer /> <!-- Consumer.svelte (Component D) -->
<script>
import { getContext } from 'svelte'
const user = getContext('user')
</script>
<p>Welcome, {user.name}!</p> That’s the entire concept: set once, access anywhere below.
Terminology
Throughout this series, we use consistent naming:
| Term | Meaning |
|---|---|
| Provider | A component that calls setContext() to broadcast data |
| Consumer | A component that calls getContext() to receive data |
| Intermediate | A component between provider and consumer that doesn’t use the context |
Prop Drilling
The problem the Context API solves is prop drilling means that data must be passed through every component in the chain. Lets look at an example without context:
<!-- Provider.svelte -->
<script>
import Intermediate from './Intermediate.svelte'
const user = { name: 'Alice', email: 'alice@example.com' }
</script>
<Intermediate {user} /> <!-- Intermediate.svelte -->
<script>
import Consumer from './Consumer.svelte'
let { user } = $props() // Intermediate doesn't use this!
</script>
<Consumer {user} /> <!-- Consumer.svelte -->
<script>
let { user } = $props()
</script>
<p>Welcome, {user.name}!</p> This is prop drilling: passing data through components that don’t use it.
Why It Hurts
- Code noise:
Intermediatehas auserprop it never uses - Tight coupling:
Intermediateis now coupled to the concept of “user” - Refactoring tax: Adding
user.emailtoConsumerrequires checking every component in the chain - Testing friction: Testing
Intermediaterequires mockingusereven though it doesn’t use it
In real applications with 6+ levels of nesting, this pain multiplies significantly.
Mental Models
To better understand how context works, here are two ways of thinking about context that help you predict its behavior.
Radio Broadcasting
| Concept | Radio | Svelte Context |
|---|---|---|
| Broadcaster | Radio station | setContext() |
| Frequency | 98.7 FM | Context key |
| Content | Program/music | Context value |
| Listener | Radio receiver | getContext() |
| Coverage | Transmitter range | Descendant components |
Key behaviors:
- One-way only — Broadcasts down, never up
- Descendants only — Siblings and parents can’t receive
- Closest wins — If two ancestors set the same key, you get the nearest one
- Listening is optional — Components can ignore context they don’t need
Bulletin Board
Think of an office building where each floor has a bulletin board:
- When you need information, check your floor’s board
- If it’s not there, go up one floor and check again
- Keep going until you find it (or reach the roof with no luck)
- Each floor can post its own version, overriding what’s above
This emphasizes the lookup process: getContext() walks up the tree until it finds a match.
The Core Functions
Svelte Context API provides four context functions:
| Function | Purpose |
|---|---|
setContext(key, value) | Broadcast a value to descendants |
getContext(key) | Receive a value from an ancestor |
hasContext(key) | Check if context exists (returns boolean) |
getAllContexts() | Get all context as a Map (for debugging/forwarding) |
Critical rule: All four must be called during component initialization — the synchronous execution of your <script> block. You cannot call them in event handlers, effects, or after await.
<script>
import { setContext, getContext } from 'svelte'
// VALID - during initialization
setContext('config', { debug: true })
const user = getContext('user')
// INVALID - after initialization
function handleClick() {
setContext('late', 'value') // Error!
}
</script> Why? Context is part of the component tree’s structure, established when components mount. By the time handlers run, descendants have already tried to read context.
For detailed coverage of each function, see Providing Context and Consuming Context.
When to Use Context
Context is powerful but not always the right tool. Here’s a decision framework:
Use Context When
Data skips multiple levels — If props would pass through 3+ components that don’t use them, context eliminates the drilling.
Many components need the same data — Theme, user, locale, configuration—context provides once, access anywhere.
Building compound components — <Tabs> and <Tab> need to communicate without exposing internals as props.
Different subtrees need different values — Context’s “closest wins” lets different sections have different themes.
Use Props When
Direct parent-child — One level of passing is clear and explicit.
Explicit API — Props document what a component needs; context is implicit.
Maximum type safety — Props have better TypeScript support out of the box.
Use Stores When
Truly global state — Same data needed in unrelated parts of the tree.
Data flows upward — Context only goes down; stores work in any direction.
State persists across unmounts — Context dies with its provider; stores live independently.
Quick Decision
Need to share data?
├─ Direct parent → child? → Props
├─ Skip 2+ levels? → Context
├─ Global / bidirectional? → Stores
└─ Otherwise → Local $state Common Misconceptions
“Context is global state”
No. Context is subtree-scoped. Different parts of your app can have different values for the same key. In SvelteKit, nested layouts can override parent context:
<!-- src/routes/+layout.svelte -->
<script>
import { setContext } from 'svelte'
setContext('theme', 'light') // Default for all routes
</script>
<!-- src/routes/admin/+layout.svelte -->
<script>
import { setContext } from 'svelte'
setContext('theme', 'dark') // Override for /admin/* routes
</script> Public pages get 'light', admin pages get 'dark'.
“Context replaces props”
No. Context complements props. Use props for direct parent-child communication and explicit component APIs.
“Context is reactive by default”
No. Context captures a value once. For reactive context, pass objects with $state inside. See Making Context Reactive.
“I can set/get context anywhere”
No. Only during component initialization. Capture values during init, then use them anywhere.
Summary
Context solves prop drilling — Data goes directly from provider to consumer, skipping intermediates.
It flows downward only — From ancestors to descendants. Nearest ancestor wins.
Four functions —
setContext,getContext,hasContext,getAllContexts.Timing matters — Must call during component initialization.
Choose wisely — Context for subtree data, props for direct communication, stores for global state.
See Also
Official Documentation
Related Articles
- $state Rune — Reactive state fundamentals
- Stores — Alternative state management