JAVASCRIPT

Managing Global Theming with Provide/Inject in Deep Component Trees

Utilize Vue 3's `provide` and `inject` to efficiently pass theme-related data down to deeply nested components without prop drilling, maintaining clean and scalable code.

// Composables/useTheme.js
import { inject, provide, ref, readonly } from 'vue'

const ThemeSymbol = Symbol('theme')

export function provideTheme(initialTheme = 'light') {
  const theme = ref(initialTheme)

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

  // Provide a readonly version of theme and the toggle function
  provide(ThemeSymbol, { theme: readonly(theme), toggleTheme })
}

export function useTheme() {
  const themeContext = inject(ThemeSymbol)
  if (!themeContext) {
    throw new Error('useTheme must be used within a component that provides a theme.')
  }
  return themeContext
}

// App.vue (Root component)
<template>
  <div :class="['app-container', theme]">
    <h1>Vue 3 Theming Example</h1>
    <button @click="toggleTheme">Toggle Theme (Current: {{ theme }})</button>
    <ParentComponent />
  </div>
</template>

<script setup>
import { provideTheme, useTheme } from './composables/useTheme'
import ParentComponent from './components/ParentComponent.vue'
import { onMounted } from 'vue'

// Provide theme context at the root
provideTheme('light')

// Use theme context in the root component itself
const { theme, toggleTheme } = useTheme()

// Example of setting initial theme from localStorage
onMounted(() => {
  const savedTheme = localStorage.getItem('app-theme')
  if (savedTheme && savedTheme !== theme.value) {
    theme.value = savedTheme // Directly update ref, or call a specific action if the composable had one.
  }
})
</script>

<style>
.app-container.light { background-color: #f0f2f5; color: #333; }
.app-container.dark { background-color: #333; color: #f0f2f5; }
button { margin: 10px; padding: 8px 15px; }
</style>

// components/ParentComponent.vue
<template>
  <div class="parent-card">
    <h2>Parent Component</h2>
    <ChildComponent />
  </div>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue'
</script>

<style scoped>
.parent-card { border: 1px solid #ccc; padding: 15px; margin: 10px; background-color: var(--card-bg-color); }
.app-container.light .parent-card { --card-bg-color: #fff; }
.app-container.dark .parent-card { --card-bg-color: #555; }
</style>

// components/ChildComponent.vue
<template>
  <div class="child-card">
    <h3>Child Component</h3>
    <p>Current Theme: {{ theme }}</p>
    <GrandchildComponent />
  </div>
</template>

<script setup>
import { useTheme } from '../composables/useTheme'
import GrandchildComponent from './GrandchildComponent.vue'

const { theme } = useTheme()
</script>

<style scoped>
.child-card { border: 1px dashed #aaa; padding: 10px; margin: 10px; background-color: var(--card-bg-color); }
.app-container.light .child-card { --card-bg-color: #f7f7f7; }
.app-container.dark .child-card { --card-bg-color: #666; }
</style>

// components/GrandchildComponent.vue
<template>
  <div class="grandchild-box">
    <h4>Grandchild Component</h4>
    <p>Theme from inject: {{ theme }}</p>
    <button @click="toggleTheme">Toggle Theme from Grandchild</button>
  </div>
</template>

<script setup>
import { useTheme } from '../composables/useTheme'

const { theme, toggleTheme } = useTheme()
</script>

<style scoped>
.grandchild-box { border: 1px dotted #888; padding: 8px; margin: 8px; background-color: var(--card-bg-color); }
.app-container.light .grandchild-box { --card-bg-color: #eee; }
.app-container.dark .grandchild-box { --card-bg-color: #777; }
</style>
How it works: This snippet demonstrates the `provide`/`inject` mechanism in Vue 3 for managing global state like a theme across deeply nested components without cumbersome prop drilling. A custom composable `useTheme.js` defines a `Symbol` for the injection key, `provideTheme` to establish the theme context with a reactive `ref` and a `toggleTheme` function at a parent level (typically the root `App.vue`), and `useTheme` to inject this context in any descendant component, regardless of its depth. By making the provided `theme` `readonly`, we ensure that only the `toggleTheme` action can modify it, maintaining data flow predictability.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs