Passing data deep: provide and inject
Sometimes data is needed by a component several levels down: App.vue → TodoList.vue → TodoItem.vue. Passing it through the props of every intermediate component is tedious. The provide function makes a value available to all descendants, and inject takes it out where it is needed.
Providing a value
In src/App.vue call provide with a key and a value:
<!-- src/App.vue -->
<script setup>
import { ref, provide } from 'vue'
import TodoList from './components/TodoList.vue'
const theme = ref('light')
provide('theme', theme)
</script>
<template>
<button @click="theme = theme === 'light' ? 'dark' : 'light'">
Switch theme
</button>
<TodoList />
</template>The key 'theme' is the name descendants use to find the value. The ref itself is passed, so descendants see its changes.
Injecting a value
In any nested component, for example src/components/TodoItem.vue, call inject with the same key:
<!-- src/components/TodoItem.vue -->
<script setup>
import { inject } from 'vue'
defineProps({ todo: Object })
const theme = inject('theme')
</script>
<template>
<li :class="theme">{{ todo.text }}</li>
</template>
<style scoped>
.dark {
background: #222;
color: #eee;
}
</style>The "Switch theme" button in App.vue switches the background of every task to dark and back. TodoList knows nothing about the theme and does not pass it on.
A default value
If no ancestor called provide with this key, inject returns undefined and Vue prints a warning. The second argument sets a fallback value:
<script setup>
import { inject } from 'vue'
const theme = inject('theme', 'light')
</script>This way the component also works outside App.vue, for example in a test.
Changes only by the owner
To keep descendants from changing the shared value at will, pass a function for changing it along with the value:
<!-- src/App.vue -->
<script setup>
import { ref, provide, readonly } from 'vue'
const theme = ref('light')
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
provide('theme', { theme: readonly(theme), toggleTheme })
</script><!-- src/components/ThemeButton.vue -->
<script setup>
import { inject } from 'vue'
const { theme, toggleTheme } = inject('theme')
</script>
<template>
<button @click="toggleTheme">Theme: {{ theme }}</button>
</template>readonly returns a version of the ref that cannot be changed. The theme can be changed only through toggleTheme — the change logic stays in one place.
When to use what
| Situation | Approach |
|---|---|
| A direct child needs the data | props |
| One value for a whole branch of the tree: theme, language | provide and inject |
| Data shared by the whole app: user, cart | A Pinia store (see the Pinia chapter) |
Common mistake
Passing a value instead of the ref to provide: provide('theme', theme.value). Descendants get the string 'light' as it was at the time of the call and never learn about the theme switch. Pass the ref itself — that keeps the link.