• Fri, Mar 2026

Using Vue.js Provide/Inject for Dependency Injection

Using Vue.js Provide/Inject for Dependency Injection

If you’ve been building apps with Vue.js for a while, you probably know that passing data from parent to child is done via props. It’s clean, explicit, and beginner-friendly. But what happens when you need to pass data down several layers of components? That’s where things can get messy with "prop drilling." This is where Vue’s provide and inject system shines. It’s Vue’s built-in way of handling dependency injection, making your components more maintainable, flexible, and scalable.

What is Provide/Inject in Vue.js?

In Vue.js, provide and inject form a simple but powerful API for sharing data across components without explicitly passing props down the component tree. Essentially, the parent (or ancestor) component provides a piece of data, and any child (or descendant) component can inject that data without requiring it to be explicitly passed through intermediate components.

Why Use Provide/Inject?

  • Removes the need for prop drilling.
  • Encourages loosely coupled components.
  • Useful for things like global services, themes, or shared state.
  • Enables reusable libraries (like form handling or modal managers).

Basic Example of Provide and Inject

Let’s start with a simple example. Imagine we have a parent component that provides a message, and a deeply nested child that needs to use it.


// ParentComponent.vue
<template>
  <div>
    <h2>Parent Component</h2>
    <ChildComponent />
  </div>
</template>

<script setup>
import { provide } from 'vue'
import ChildComponent from './ChildComponent.vue'

provide('message', 'Hello from Parent!')
</script>

// ChildComponent.vue
<template>
  <div>
    <p>Injected Message: {{ message }}</p>
  </div>
</template>

<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>

In this example, the ParentComponent provides the message, and the ChildComponent injects it directly. Notice that the message wasn’t passed down as props.

Using Provide/Inject with Reactive Data

One key thing to understand is that provide doesn’t automatically make values reactive. If you want reactivity, you should provide a ref or reactive object.


// ParentComponent.vue
<script setup>
import { provide, ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const theme = ref('light')

provide('theme', theme)
</script>

<template>
  <div>
    <button @click="theme.value = theme.value === 'light' ? 'dark' : 'light'">
      Toggle Theme
    </button>
    <ChildComponent />
  </div>
</template>

// ChildComponent.vue
<script setup>
import { inject } from 'vue'

const theme = inject('theme')
</script>

<template>
  <div>
    <p>Current Theme: {{ theme }}</p>
  </div>
</template>

Now, whenever the button is clicked in the parent, the theme toggles, and the injected child will update automatically because we passed a ref.

Default Values in Inject

Sometimes, a child may need a default value if no provider is available. Vue lets you specify defaults like this:


const message = inject('message', 'Default Message')

This way, the component won’t break even if there’s no provider.

Real-World Example: Building a Notification System with Provide/Inject

Let’s put all of this into practice by creating a reusable notification system. We’ll provide a function for adding notifications and inject it wherever we need to trigger one.

Step 1: Create a Notification Provider


// NotificationProvider.vue
<script setup>
import { ref, provide } from 'vue'

const notifications = ref([])

function addNotification(message, type = 'info') {
 notifications.value.push({ message, type, id: Date.now() })
}

provide('addNotification', addNotification)
provide('notifications', notifications)
</script>

<template>
 <div >
  <div v-for="n in notifications" :key="n.id" >
   <strong>{{ n.type.toUpperCase() }}:</strong> {{ n.message }}
  </div>
 </div>
 <slot />
</template>

Step 2: Inject and Use Notifications in Another Component


// ActionComponent.vue
<script setup>
import { inject } from 'vue'

const addNotification = inject('addNotification')
function triggerAction() {
  addNotification('Action completed successfully!', 'success')
}
</script>

<template>
  <button @click="triggerAction">Do Action</button>
</template>

Step 3: Wrap Your App with the Provider


// App.vue
<template>
  <NotificationProvider>
    <ActionComponent />
  </NotificationProvider>
</template>

<script setup>
import NotificationProvider from './NotificationProvider.vue'
import ActionComponent from './ActionComponent.vue'
</script>

With this setup, the ActionComponent doesn’t need to know anything about how notifications are managed. It simply calls addNotification. That’s the power of dependency injection.

Best Practices with Provide/Inject

  • Use sparingly: Don’t replace props entirely. Provide/Inject is best for shared or global-like data.
  • Keep keys descriptive: Use meaningful keys like userService instead of generic names.
  • Combine with composables: Provide services as functions or composables for more flexibility.
  • Use TypeScript (if possible): Strong typing makes dependency injection safer and easier to debug.

Full Real-World Example: Theme Manager

Let’s create a complete theme manager using Provide/Inject.


// ThemeProvider.vue
<script setup>
import { ref, provide } from 'vue'

const theme = ref('light')

function toggleTheme() {
 theme.value = theme.value === 'light' ? 'dark' : 'light'
}

provide('theme', theme)
provide('toggleTheme', toggleTheme)
</script>

<template>
 <div :>
  <slot />
 </div>
</template>

// ThemeSwitcher.vue
<script setup>
import { inject } from 'vue'

const theme = inject('theme')
const toggleTheme = inject('toggleTheme')
</script>

<template>
  <button @click="toggleTheme">
    Current Theme: {{ theme }}
  </button>
</template>

// App.vue
<template>
  <ThemeProvider>
    <ThemeSwitcher />
    <p>This text will adapt with the theme.</p>
  </ThemeProvider>
</template>

<script setup>
import ThemeProvider from './ThemeProvider.vue'
import ThemeSwitcher from './ThemeSwitcher.vue'
</script>

This theme manager demonstrates how provide/inject can power real-world UI features like global theme switching without messy prop drilling.

Conclusion

Vue.js provide and inject is a hidden gem for dependency injection. It helps simplify communication across deeply nested components, reduces boilerplate, and encourages loosely coupled, reusable code. Whether you’re building a notification system, theme manager, or shared services, this API will make your Vue apps more maintainable and scalable.

This website uses cookies to enhance your browsing experience. By continuing to use this site, you consent to the use of cookies. Please review our Privacy Policy for more information on how we handle your data. Cookie Policy