Tutorials Logic, IN info@tutorialslogic.com

Vue Provide Inject Dependency Injection

Dependency Channel

provide and inject pass a dependency through a component subtree without forwarding it through every intermediate prop. This fits stable contextual services such as form registration, theme controls, or a parent-owned coordination API. It should not become invisible global state for unrelated features.

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

Provide / Inject - Theme, Locale, App Config

Provide / Inject - Theme, Locale, App Config
<!-- 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>

What is Provide / Inject? - HTML Example

What is Provide / Inject? - HTML Example
<!-- 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) -->

What is Provide / Inject? - JavaScript Example

What is Provide / Inject? - JavaScript Example
// 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()

Injection Contract

Use a Symbol as the injection key in larger applications and export the key with the dependency type. Provide a readonly view of state when descendants should request changes through explicit actions. Supply a meaningful default only when absence is a valid mode; otherwise fail early with a clear developer error.

The closest ancestor providing the same key wins, which enables scoped overrides in nested subtrees and tests. Keep mutations near the provider so ownership remains clear. Use Pinia instead when distant routes share a domain store and need devtools, persistence policy, or application-level lifecycle.

Before you move on

Vue Provide Inject Dependency Injection Mastery Check

4 checks
  • 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.
Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.