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.

Need help integrating this into your project?

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

Hire DigitalCodeLabs