JAVASCRIPT

Vue 3 Dynamic Theming with provide/inject

Implement dynamic theming in Vue 3 applications using CSS variables and the `provide`/`inject` pattern for global, reactive theme management.

// src/composables/useTheme.js
import { ref, provide, inject, watch } from 'vue';

const ThemeSymbol = Symbol('theme');

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

  watch(theme, (newTheme) => {
    // Apply CSS variables to the root element (document.documentElement)
    if (typeof document !== 'undefined') {
      const root = document.documentElement;
      root.setAttribute('data-theme', newTheme); // For semantic class/data attribute
      // You can also directly set CSS variables here if preferred over data-theme:
      // if (newTheme === 'dark') {
      //   root.style.setProperty('--primary-bg', '#333333');
      //   root.style.setProperty('--text-color', '#f0f0f0');
      // } else {
      //   root.style.setProperty('--primary-bg', '#f0f0f0');
      //   root.style.setProperty('--text-color', '#333333');
      // }
    }
  }, { immediate: true }); // Apply theme immediately on setup

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

  provide(ThemeSymbol, { theme, toggleTheme });
}

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

/* Example App.vue (Parent Component) */
// <template>
//   <header>
//     <h1>My Themed App</h1>
//     <button @click="toggleTheme">Toggle Theme ({{ theme }})</button>
//   </header>
//   <main>
//     <ChildComponent />
//   </main>
// </template>
//
// <script setup>
// import { provideTheme } from './composables/useTheme';
// import { useTheme } from './composables/useTheme'; // To use it in the providing component too
// import ChildComponent from './components/ChildComponent.vue';
//
// provideTheme('light'); // Initialize theme for the app
// const { theme, toggleTheme } = useTheme(); // Use it to display/toggle in parent
// </script>

/* Example ChildComponent.vue */
// <template>
//   <div :class="['card', theme === 'dark' ? 'dark-card' : 'light-card']">
//     <p>Current theme from inject: {{ theme }}</p>
//     <button @click="toggleTheme">Toggle Theme from Child</button>
//   </div>
// </template>
//
// <script setup>
// import { useTheme } from '../composables/useTheme';
// const { theme, toggleTheme } = useTheme();
// </script>

/* Example CSS (e.g., in main.css or App.vue style block) */
// :root[data-theme='light'] {
//   --primary-bg: #f0f0f0;
//   --text-color: #333;
//   --card-bg: #ffffff;
// }
// :root[data-theme='dark'] {
//   --primary-bg: #333333;
//   --text-color: #f0f0f0;
//   --card-bg: #444444;
// }
//
// body {
//   background-color: var(--primary-bg);
//   color: var(--text-color);
//   transition: background-color 0.3s, color 0.3s;
// }
// .card {
//   background-color: var(--card-bg);
//   border: 1px solid var(--text-color);
//   padding: 15px;
//   margin: 10px;
//   border-radius: 8px;
//   transition: background-color 0.3s, border-color 0.3s;
// }
How it works: This snippet demonstrates how to implement dynamic theming in a Vue 3 application using a combination of the Composition API's `provide` and `inject` functions, along with CSS variables. A `provideTheme` composable initializes and manages the current theme, making it reactively available to all descendant components via `inject` through a unique `Symbol`. A `watch` effect updates a `data-theme` attribute on the `document.documentElement`, which CSS rules can then use to apply different styles via CSS variables (`--primary-bg`, `--text-color`, etc.). This pattern allows for a globally managed, easily switchable theme without prop drilling, enhancing modularity and reusability.

Need help integrating this into your project?

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

Hire DigitalCodeLabs