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.