How to create a theme switcher in Vue

Theme switchers enable users to customize application appearance with multiple color schemes, enhancing personalization and brand flexibility. As the creator of CoreUI, a widely used open-source UI library, I’ve built theme switchers for enterprise Vue applications throughout my 11 years of frontend development. The most versatile approach is managing theme state reactively and applying theme-specific CSS custom properties. This method supports unlimited themes, smooth transitions, and maintains persistent user preferences across sessions.

Define multiple theme configurations, use reactive state for active theme, and apply theme-specific CSS variables dynamically.

<script setup>
import { ref, onMounted, watch } from 'vue'

const themes = {
  light: {
    '--bg-color': '#ffffff',
    '--text-color': '#333333',
    '--primary-color': '#0d6efd',
    '--card-bg': '#f8f9fa'
  },
  dark: {
    '--bg-color': '#1a1a1a',
    '--text-color': '#ffffff',
    '--primary-color': '#0d6efd',
    '--card-bg': '#2d2d2d'
  },
  blue: {
    '--bg-color': '#e3f2fd',
    '--text-color': '#0d47a1',
    '--primary-color': '#1976d2',
    '--card-bg': '#bbdefb'
  },
  green: {
    '--bg-color': '#e8f5e9',
    '--text-color': '#1b5e20',
    '--primary-color': '#388e3c',
    '--card-bg': '#c8e6c9'
  }
}

const currentTheme = ref('light')

const applyTheme = (themeName) => {
  const theme = themes[themeName]
  const root = document.documentElement

  Object.entries(theme).forEach(([property, value]) => {
    root.style.setProperty(property, value)
  })
}

const setTheme = (themeName) => {
  currentTheme.value = themeName
  applyTheme(themeName)
  localStorage.setItem('selectedTheme', themeName)
}

onMounted(() => {
  const savedTheme = localStorage.getItem('selectedTheme') || 'light'
  setTheme(savedTheme)
})
</script>

<template>
  <div class='app'>
    <div class='theme-switcher'>
      <button
        v-for='(theme, name) in themes'
        :key='name'
        @click='setTheme(name)'
        :class="{ active: currentTheme === name }"
      >
        {{ name.charAt(0).toUpperCase() + name.slice(1) }}
      </button>
    </div>

    <div class='content'>
      <h1>Theme Switcher Demo</h1>
      <div class='card'>
        <p>Current theme: <strong>{{ currentTheme }}</strong></p>
        <button class='primary-btn'>Primary Button</button>
      </div>
    </div>
  </div>
</template>

<style>
:root {
  --bg-color: #ffffff;
  --text-color: #333333;
  --primary-color: #0d6efd;
  --card-bg: #f8f9fa;
}

body {
  background-color: var(--bg-color);
  color: var(--text-color);
  transition: all 0.3s ease;
  margin: 0;
  font-family: system-ui, -apple-system, sans-serif;
}

.app {
  min-height: 100vh;
  padding: 2rem;
}

.theme-switcher {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 2rem;
}

.theme-switcher button {
  padding: 0.5rem 1rem;
  border: 2px solid var(--primary-color);
  background: transparent;
  color: var(--text-color);
  border-radius: 0.375rem;
  cursor: pointer;
  transition: all 0.2s;
}

.theme-switcher button.active {
  background: var(--primary-color);
  color: white;
}

.card {
  background: var(--card-bg);
  padding: 1.5rem;
  border-radius: 0.5rem;
}

.primary-btn {
  background: var(--primary-color);
  color: white;
  border: none;
  padding: 0.75rem 1.5rem;
  border-radius: 0.375rem;
  cursor: pointer;
  margin-top: 1rem;
}
</style>

Here the themes object defines multiple color schemes with CSS custom property values for each theme. The currentTheme ref tracks the active theme name. The applyTheme function iterates through theme properties and applies them to document root using setProperty. The setTheme function updates the active theme, applies CSS variables, and persists the selection to localStorage. The v-for directive renders a button for each available theme with active state highlighting. All components use CSS custom properties (var(–property)) that update automatically when themes change.

Best Practice Note:

This is the theme switcher architecture we use in CoreUI Pro applications for flexible multi-brand support and user customization. Extend theme objects to include font families and spacing variables for comprehensive theming, use provide/inject to create a global theme composable accessible throughout your application, and implement theme preview on hover for better user experience when selecting themes.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team