Provide / Inject
What is Provide / Inject?
provide and inject solve the prop drilling problem — passing data through many layers of components that don't need it. A parent component provides data, and any descendant (no matter how deep) can inject it directly.
Unlike props, provide/inject skips intermediate components entirely. It's Vue's built-in dependency injection system.
| Feature | Props | Provide/Inject | Pinia |
|---|---|---|---|
| Scope | Parent → direct child | Ancestor → any descendant | Global |
| Reactivity | Yes | Yes (with ref/reactive) | Yes |
| Best for | Direct parent-child | Plugin-like data, theme, locale | App-wide state |
<!-- App.vue — provides data to all descendants -->
<template>
<div :data-theme="theme">
<Navbar />
<Main />
<Footer />
</div>
</template>
<script setup>
import { ref, provide, readonly } from 'vue'
// Provide reactive data
const theme = ref('light')
const locale = ref('en')
const user = ref({ name: 'Alice', role: 'admin' })
// Provide a function to update theme (keeps mutation in provider)
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
// provide(key, value)
provide('theme', readonly(theme)) // read-only to prevent mutation
provide('toggleTheme', toggleTheme) // function to update
provide('locale', locale)
provide('currentUser', readonly(user))
// Provide an object with multiple values
provide('appConfig', {
apiUrl: 'https://api.example.com',
version: '2.0.0',
features: { darkMode: true, notifications: true }
})
</script>
<!-- DeepChild.vue — can be nested 10 levels deep, still works -->
<template>
<div :class="`theme-${theme}`">
<p>Theme: {{ theme }}</p>
<p>User: {{ currentUser?.name }}</p>
<p>API: {{ appConfig?.apiUrl }}</p>
<button @click="toggleTheme">Toggle Theme</button>
</div>
</template>
<script setup>
import { inject } from 'vue'
// inject(key, defaultValue)
const theme = inject('theme', 'light') // with default
const toggleTheme = inject('toggleTheme', () => {}) // with default
const currentUser = inject('currentUser')
const appConfig = inject('appConfig')
// inject returns undefined if not provided (no error)
const locale = inject('locale')
</script>
<!-- Symbol keys — avoid naming collisions in large apps -->
<!-- keys.js -->
<!-- export const THEME_KEY = Symbol('theme') -->
<!-- export const USER_KEY = Symbol('user') -->
<!-- Provider: provide(THEME_KEY, theme) -->
<!-- Consumer: const theme = inject(THEME_KEY) -->
// composables/useTheme.js — wrap provide/inject in composables
import { ref, provide, inject, readonly } from 'vue'
const THEME_KEY = Symbol('theme')
// Used in the provider component (App.vue or layout)
export function provideTheme() {
const theme = ref('light')
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
function setTheme(newTheme) {
theme.value = newTheme
}
provide(THEME_KEY, {
theme: readonly(theme),
toggleTheme,
setTheme,
})
return { theme, toggleTheme, setTheme }
}
// Used in any descendant component
export function useTheme() {
const context = inject(THEME_KEY)
if (!context) {
throw new Error('useTheme() must be used within a component that calls provideTheme()')
}
return context
}
// Usage in App.vue:
// import { provideTheme } from '@/composables/useTheme'
// provideTheme()
// Usage in any child:
// import { useTheme } from '@/composables/useTheme'
// const { theme, toggleTheme } = useTheme()
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.