JAVASCRIPT
Implement Dynamic Theming with Vue 3 Provide/Inject and CSS Variables
Set up a flexible dynamic theming system in Vue 3 using the Composition API's `provide` and `inject` alongside CSS variables for easy style management.
// composables/useTheme.js (for global theme state)
import { ref, provide, inject, computed, onMounted } from 'vue';
const THEME_KEY = Symbol('theme'); // Unique key for injection
export function provideTheme() {
const currentTheme = ref('light'); // 'light' or 'dark'
const toggleTheme = () => {
currentTheme.value = currentTheme.value === 'light' ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', currentTheme.value);
localStorage.setItem('app-theme', currentTheme.value);
};
// Initialize theme on app start (e.g., from localStorage)
onMounted(() => {
const savedTheme = localStorage.getItem('app-theme');
if (savedTheme) {
currentTheme.value = savedTheme;
document.documentElement.setAttribute('data-theme', savedTheme);
} else {
// Set default if no theme saved
document.documentElement.setAttribute('data-theme', currentTheme.value);
}
});
provide(THEME_KEY, {
currentTheme: computed(() => currentTheme.value),
toggleTheme
});
}
export function useTheme() {
const themeContext = inject(THEME_KEY);
if (!themeContext) {
throw new Error('useTheme must be used within a component that has a theme provider.');
}
return themeContext;
}
// App.vue (or any top-level component where theme needs to be provided)
<template>
<div :class="['app-container', `theme-${currentTheme}`]">
<h1>My Awesome App</h1>
<button @click="toggleTheme">Toggle Theme</button>
<ThemedComponent />
<div style="height: 50px; background-color: var(--background-color); border: 1px solid var(--text-color); margin-top: 20px;">Themed Box</div>
</div>
</template>
<script setup>
import { provideTheme, useTheme } from './composables/useTheme';
import ThemedComponent from './components/ThemedComponent.vue';
// Provide theme context at the root of your application
// This will make `useTheme` available to all descendants
provideTheme();
// Use theme context in this component (optional, can be used deeper)
const { currentTheme, toggleTheme } = useTheme();
</script>
<style>
/* Define CSS variables for themes on the root element */
:root {
--background-color: #f0f0f0;
--text-color: #333;
--button-background: #007bff;
--button-text: white;
}
[data-theme='dark'] {
--background-color: #333;
--text-color: #f0f0f0;
--button-background: #0056b3;
--button-text: white;
}
/* Apply variables to body or main app container */
body {
margin: 0;
font-family: Arial, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
.app-container {
padding: 20px;
}
button {
background-color: var(--button-background);
color: var(--button-text);
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
margin-right: 10px;
transition: background-color 0.3s;
}
button:hover {
opacity: 0.9;
}
</style>
// components/ThemedComponent.vue (example of a child component using the theme)
<template>
<div class="themed-box">
<p>This component respects the theme.</p>
<p>Current theme from injected context: <strong>{{ currentTheme }}</strong></p>
<button @click="toggleTheme">Toggle Theme from Child</button>
</div>
</template>
<script setup>
import { useTheme } from '../composables/useTheme';
const { currentTheme, toggleTheme } = useTheme();
</script>
<style scoped>
.themed-box {
padding: 20px;
margin-top: 20px;
border: 1px solid var(--text-color);
border-radius: 8px;
background-color: var(--background-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s, border-color 0.3s;
}
</style>
How it works: This snippet demonstrates how to create a global dynamic theming system in Vue 3 using the Composition API's `provide` and `inject`. A `useTheme` composable manages the `currentTheme` reactive state, which is provided at a high level (e.g., `App.vue`). Any descendant component can then `inject` this context to read the current theme and call the `toggleTheme` function. The actual styling is managed with CSS variables, which are updated on the `documentElement` based on the active theme, allowing for seamless and efficient theme switching across the entire application without prop drilling.