provide and inject
As component trees grow deeper, passing data through props at every level — often called prop drilling — becomes tedious and brittle: intermediate components must accept and forward props they never actually use themselves, just so a distant descendant can receive them. Vue's provide/inject pair solves this by letting an ancestor component make a value available to all of its descendants, no matter how deeply nested, without threading it through every intermediate layer. Any descendant can then inject that value directly.
Cricket analogy: Passing team strategy from the head coach down through assistant coaches, fielding coaches, and finally to a fielder — each intermediate coach forwarding info they don't use — is prop drilling; provide/inject lets the head coach broadcast the strategy directly to any fielder, however deep.
Providing a value
In the Composition API, provide(key, value) is called inside setup() (or the top level of <script setup>) on the ancestor component. The key can be a string, but using a Symbol avoids naming collisions between unrelated provide/inject pairs in a large codebase. To keep injected state reactive, provide a ref or reactive object rather than a plain primitive, so descendants see updates.
Cricket analogy: The head coach calls provide('teamStrategy', strategyRef) once at the start of the tour (setup) — using a unique code name (Symbol) instead of the generic word "strategy" avoids clashing with another department's plan, and providing a live tactics board (ref) rather than a printed sheet means every update reaches fielders instantly.
<!-- ThemeProvider.vue (ancestor) -->
<script setup>
import { provide, ref } from 'vue'
const theme = ref('dark')
function toggleTheme() {
theme.value = theme.value === 'dark' ? 'light' : 'dark'
}
provide('theme', { theme, toggleTheme })
</script>Injecting a value
Any descendant component — regardless of nesting depth — calls inject(key) to retrieve the value provided by the nearest matching ancestor. If no ancestor has provided that key, inject returns undefined unless a default value is supplied as a second argument. Injected reactive values remain fully reactive, so mutating a provided ref in an ancestor updates every descendant that injected it.
Cricket analogy: A fielder anywhere on the pitch can call inject('teamStrategy') to pull the nearest coach's tactics; if no coach ever provided one, they get nothing unless a fallback default plan was specified, and when the coach updates the live tactics board, every fielder who injected it sees the change instantly.
<!-- ThemedButton.vue (deep descendant) -->
<script setup>
import { inject } from 'vue'
const { theme, toggleTheme } = inject('theme', {
theme: ref('light'),
toggleTheme: () => {}
})
</script>
<template>
<button :class="theme" @click="toggleTheme">Toggle theme</button>
</template>Read-only provided state
Because injected state can technically be mutated by any descendant, large applications often wrap provided values with readonly() to prevent descendants from silently modifying shared state, forcing them instead to call a provided function (like toggleTheme above) that performs the mutation in a controlled way. This keeps the data flow predictable even though provide/inject bypasses the strict parent-to-child prop chain.
Cricket analogy: Because any fielder could technically overwrite the injected tactics board, a smart coach wraps it with readonly() and instead provides a callSignal() function, so fielders request changes through the coach rather than silently rewriting the plan themselves.
provide/inject is conceptually equivalent to React's Context API (createContext + useContext) or Angular's hierarchical dependency injection. Like React Context, it is best reserved for cross-cutting concerns — themes, localization, authenticated user info — rather than as a general replacement for props and emits in ordinary parent-child relationships.
Overusing provide/inject for everyday component communication makes data flow hard to trace, since a descendant's injected value could originate from any ancestor in the tree, not just its immediate parent. Prefer explicit props/emits for direct parent-child relationships, and reserve provide/inject for genuinely cross-cutting or deeply-nested-only data.
- provide/inject lets an ancestor share data with any descendant, bypassing prop drilling through intermediate components.
- provide(key, value) is called in an ancestor's setup/script setup; inject(key, default) is called in any descendant.
- Using Symbol keys avoids naming collisions across independent provide/inject pairs in large apps.
- Providing a ref or reactive object keeps injected data reactive across the whole descendant subtree.
- Wrapping provided state with readonly() prevents descendants from mutating shared state directly.
- It is the Vue equivalent of React Context or Angular's hierarchical DI, best used for cross-cutting concerns.
Practice what you learned
1. What problem does provide/inject primarily solve?
2. Where is provide() typically called?
3. Why might you use a Symbol instead of a string as a provide/inject key?
4. What happens if inject('theme') is called but no ancestor has provided 'theme'?
5. What is the purpose of wrapping a provided value with readonly()?
Was this page helpful?
You May Also Like
reactive() and ref()
A deep dive into Vue 3's two core reactivity primitives — ref() for any value type and reactive() for objects — and when to use each.
Props Explained
Explains how props pass data from parent to child components in Vue, covering declaration, validation, defaults, and the one-way data flow rule.
Pinia Basics
Get started with Pinia, Vue's official state management library, covering stores, state, and how components read and mutate shared data.
Sharing State Across Components
Compare the tools Vue offers for cross-component state sharing — props/emits, provide/inject, composables, and Pinia — and when to reach for each.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics