How to implement dark mode in Vue

Dark mode improves user experience by reducing eye strain in low-light environments and has become an essential feature for modern web applications. As the creator of CoreUI, a widely used open-source UI library, I’ve implemented dark mode in Vue applications throughout my 11 years of frontend development. The most effective approach is using Vue 3 Composition API with reactive state and CSS custom properties for theme switching. This method enables smooth theme transitions, persists user preferences, and maintains clean separation between logic and styling.

Use reactive state to track dark mode, toggle theme class on root element, and define CSS variables for light and dark themes.

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

const isDarkMode = ref(false)

const toggleDarkMode = () => {
  isDarkMode.value = !isDarkMode.value
}

watch(isDarkMode, (newValue) => {
  if (newValue) {
    document.documentElement.classList.add('dark-mode')
    localStorage.setItem('theme', 'dark')
  } else {
    document.documentElement.classList.remove('dark-mode')
    localStorage.setItem('theme', 'light')
  }
})

onMounted(() => {
  const savedTheme = localStorage.getItem('theme')
  isDarkMode.value = savedTheme === 'dark'
})
</script>

<template>
  <div class='app'>
    <button @click='toggleDarkMode'>
      {{ isDarkMode ? '☀️ Light Mode' : '🌙 Dark Mode' }}
    </button>
    <h1>Welcome to the App</h1>
    <p>This content changes with the theme</p>
  </div>
</template>

<style>
:root {
  --bg-color: #ffffff;
  --text-color: #333333;
  --card-bg: #f5f5f5;
}

.dark-mode {
  --bg-color: #1a1a1a;
  --text-color: #ffffff;
  --card-bg: #2d2d2d;
}

body {
  background-color: var(--bg-color);
  color: var(--text-color);
  transition: background-color 0.3s, color 0.3s;
}

.app {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
}

button {
  background-color: var(--card-bg);
  color: var(--text-color);
  border: none;
  padding: 0.75rem 1.5rem;
  border-radius: 0.5rem;
  cursor: pointer;
}
</style>

Here the isDarkMode ref tracks the current theme state reactively. The watch function responds to theme changes by adding or removing the dark-mode class on the document root element and persisting the preference to localStorage. The onMounted lifecycle hook restores the saved theme preference when the component loads. CSS custom properties (–bg-color, –text-color) define theme colors that update automatically when the dark-mode class is applied. The transition property creates smooth color changes when switching themes.

Best Practice Note:

This is the dark mode implementation pattern we use in CoreUI Vue components for consistent theming across enterprise applications. Consider detecting system preference with window.matchMedia(’(prefers-color-scheme: dark)’) to set initial theme, use provide/inject to share theme state across deeply nested components, and implement CSS transitions on color properties for smooth visual transitions between 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.
How to sleep in Javascript
How to sleep in Javascript

How to dynamically add, remove, and toggle CSS classes in React.js
How to dynamically add, remove, and toggle CSS classes in React.js

How to Remove Elements from a JavaScript Array
How to Remove Elements from a JavaScript Array

How to loop through a 2D array in JavaScript
How to loop through a 2D array in JavaScript

Answers by CoreUI Core Team