Skip Prop Drilling Across Deep Trees
provide & inject — Dependency Injection
provide makes a value available to every descendant component; inject reads it. Useful for cross-cutting concerns without prop drilling.
What you'll learn
- Provide a value from an ancestor
- Inject by key (use a Symbol for type safety)
- Type provides with InjectionKey
provide and inject are Vue’s built-in dependency-injection mechanism. An ancestor provides a value under a key; any descendant can inject it without passing props down through every layer.
The Basic Pattern
<!-- App.vue -->
<script setup>
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('theme', theme)
</script>
<template>
<ThemedPanel />
</template> <!-- DeepChild.vue -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme', 'light') // 'light' is the default
</script>
<template>
<p>Current theme: {{ theme }}</p>
</template> Because theme is a ref, descendants automatically re-render when it changes.
Type-Safe Keys With InjectionKey
String keys are fragile — typos crash silently. Use a Symbol typed as InjectionKey<T> to get full TypeScript inference.
// keys.ts
import type { InjectionKey, Ref } from 'vue'
export const ThemeKey: InjectionKey<Ref<string>> = Symbol('theme') // provider
import { provide, ref } from 'vue'
import { ThemeKey } from './keys'
const theme = ref('dark')
provide(ThemeKey, theme) // consumer
import { inject } from 'vue'
import { ThemeKey } from './keys'
const theme = inject(ThemeKey) // typed as Ref<string> | undefined App-Level Provide
You can provide values from the app instance — every component sees them.
// main.ts
app.provide('apiUrl', 'https://api.example.com') When to Reach For It
- Theming, i18n locale, current user — values needed everywhere.
- Plugin APIs that don’t fit cleanly as props.
Use props for normal parent-child data — provide/inject creates implicit coupling. For app-wide reactive state, prefer a Pinia store.
ref vs reactive — Which to Pick →