Creating a Dynamic Theme Switcher in Vue 3 with CSS Variables
As the demand for user-friendly interfaces grows, so does the expectation for applications to support multiple themes, such as dark mode and light mode. A dynamic theme switcher not only enhances user experience but also adds a level of personalization to your application. In this article, we’ll explore how to build a dynamic theme switcher in Vue 3 using CSS variables, allowing users to switch between dark mode, light mode, and custom themes.
Step 1: Defining CSS Variables for Themes
CSS variables (also known as CSS custom properties) are perfect for defining theme styles because they can be dynamically updated in real-time. Let’s define some CSS variables for light and dark themes in your src/assets/styles.css
(or wherever you prefer to store your global styles).
//styles.css
:root {
--background-color: #ffffff;
--text-color: #000000;
--primary-color: #d2e8de;
}
[data-theme="dark"] {
--background-color: #090808;
--text-color: #efefef;
--primary-color: #374241;
}
[data-theme="grape"] {
--background-color: #9919c4;
--text-color: #8c64dc;
--primary-color: #490e81;
}
[data-theme="lemon"] {
--background-color: #be9523;
--text-color: #e5df6c;
--primary-color: #ea9e2c;
}
In this example, we’ve defined several themes. Each theme modifies the same set of CSS variables (--background-color
, --text-color
, and --primary-color
).
To apply these themes globally, we’ll need to import the style sheet into main.js
//main.sj
import { createApp } from 'vue'
import App from './App.vue'
import './assets/styles.css'
createApp(App).mount('#app')
Step 2: Creating the Theme Switcher Component
Create a new component called ThemeSwitcher.vue
inside your src/components
directory:
<script setup>
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/stores/themeStore.js';
import { watch } from "vue";
const themeStore = useThemeStore();
const { currentTheme } = storeToRefs(themeStore);
const updateTheme = (theme) => {
themeStore.setTheme(theme);
};
watch(currentTheme, (newTheme) => {
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});
</script>
<template>
<div class="theme-switcher">
<label>
<input type="radio" name="theme" value="light" @change="updateTheme('light')" :checked="currentTheme === 'light'" />
Light
</label>
<label>
<input type="radio" name="theme" value="dark" @change="updateTheme('dark')" :checked="currentTheme === 'dark'" />
Dark
</label>
<label>
<input type="radio" name="theme" value="grape" @change="updateTheme('grape')" :checked="currentTheme === 'grape'" />
Grape
</label>
<label>
<input type="radio" name="theme" value="lemon" @change="updateTheme('lemon')" :checked="currentTheme === 'lemon'" />
Lemon
</label>
<div class="card">
<h2>Card Title</h2>
<p>This is a card component. The theme switcher will change its styles.</p>
<button class="primary-button">Primary Button</button>
</div>
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" placeholder="Enter your name" />
</div>
</div>
</template>
<style>
h1 {
color: var(--primary-color);
margin-bottom: 20px;
}
.card {
background-color: var(--background-color);
border: 1px solid var(--primary-color);
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.primary-button {
background-color: var(--primary-color);
color: var(--text-color);
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease, color 0.3s ease;
}
.primary-button:hover {
background-color: darken(var(--primary-color), 10%);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: var(--text-color);
}
.form-group input {
padding: 10px;
border: 1px solid var(--primary-color);
border-radius: 4px;
width: 100%;
transition: border-color 0.3s ease, background-color 0.3s ease, color 0.3s ease;
}
.form-group input:focus {
outline: none;
border-color: darken(var(--primary-color), 10%);
background-color: lighten(var(--background-color), 5%);
color: var(--text-color);
}
</style>
This component allows users to select between the custom themes. The currentTheme
ref stores the currently selected theme. The updateTheme
function is called with the newly selected theme name and updates currentTheme
and as well as thedata-theme
attribute on the <html>
element, which triggers the corresponding CSS variables. Additionally, the theme is set to be defaulted as “dark” mode. With modifications, these settings could be used with local storage or state stores to allow persistent settings.
Step 3: Using the Theme Switcher Component
To integrate the theme switcher into your application, include it in App.vue
or another global component:
//App.vue
<template>
<div id="app">
<ThemeSwitcher />
</div>
</template>
<script setup>
import ThemeSwitcher from './components/ThemeSwitcher.vue';
</script>
<style>
#app {
background-color: var(--background-color);
color: var(--text-color);
padding: 20px;
transition: background-color 0.3s ease, color 0.3s ease;
}
h1 {
color: var(--primary-color);
}
</style>
The background color, text color, and primary color are now driven by the CSS variables, which change dynamically based on the active theme. The transition effect on the background and text color creates a smooth visual experience when switching themes.
Step 4: Extending and Customizing Themes
With this setup, adding new themes or customizing existing ones is straightforward. Simply define additional CSS variables for your new themes in styles.css
and update the ThemeSwitcher.vue
component to include the new options.
For example, to add a “blue” theme, edit styles to include:
[data-theme="blueberry"] {
--background-color: #0d4fde;
--text-color: #0ec3e3;
--primary-color: #0c408d;
}
Add the option in ThemeSwitcher.vue
:
<label>
<input type="radio" name="theme" value="blueberry" @change="updateTheme('blueberry')"
:checked="currentTheme === 'blueberry'"/>
Blueberry
</label>
Conclusion
Creating a dynamic theme switcher in Vue 3 using CSS variables is a straightforward yet powerful way to enhance the user experience. By leveraging Vue 3’s reactivity and the flexibility of CSS variables, you can build a highly customizable theme management system that can easily adapt to user preferences.
This approach not only improves the visual appeal of your application but also provides users with a more personalized experience, which is becoming increasingly important in modern web development. With the ability to manage theme state globally, extend the system with additional themes, and persist user preferences across sessions, you now have the foundation for a robust theming system in your Vue 3 applications.