DEV Community

Cover image for JS/Vue Dark Theme Switcher
Jason C
Jason C

Posted on

JS/Vue Dark Theme Switcher

I recently added a dark theme to one of our internal web apps. It uses Vue 2 and Bootstrap 4. We already had a light bootstrap theme with a few customizations. So all I needed was a new dark theme and a way to switch between them.

To make life simple, I renamed the current theme to 'light.css' and added the new theme as 'dark.css'. These are both minimized bootstrap 4 themes.

Here is the code used to preload the themes into link/style sheet tags. It also handles reading the user's preferred color scheme and uses local storage in case they want to change it. When selecting a theme it sets an attribute on the body tag (data-theme) so we can override things in CSS.

export const knownThemes = ['light', 'dark'];
const themes = {};

export class Themes {
    constructor() {
        const tasks = => {
            return this.preloadTheme(n).then(s => {
                themes[n] = s;
        Promise.all(tasks).then(t => {
            const userPref = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
            const useTheme = localStorage.getItem("theme") || userPref || knownThemes[0];

    preloadTheme(name) {
        return new Promise((resolve, reject) => {
            const id = 'theme-' + name;
            let link = document.createElement('link');
   = id;
            link.rel = "stylesheet";
            link.href = '/themes/' + name + '.css';
            link.onload = e => {
                const sheet = document.getElementById(id);
                sheet.disabled = true;
            link.onerror = reject;

    select(name) {
        if (name && !themes[name]) {
            throw new Error(`"${name}" has not been defined as a theme.`);
        let body = document.body;        
        Object.keys(themes).forEach(n => themes[n].disabled = (n !== name));
        body.setAttribute("data-theme", name);
        localStorage.setItem("theme", name);        

    _watchSystemChanges() {
        if (!window.matchMedia) return;
        window.matchMedia('(prefers-color-scheme: dark)').addListener(e => {
            if (e.matches) {
            } else {

    get names() {
        return knownThemes;

Enter fullscreen mode Exit fullscreen mode

now in our global css (outside of the themes), we can write things like this:

/* Dark theme overrides */
[data-theme="dark"] .someClass {
    background-color: rgb(0,0,0);
    border: 1px solid #6c757d;
    color: #dee2e6;

[data-theme="dark"] a {
    color: #96C93E;

[data-theme="light"] a {
    color: #007bff;
Enter fullscreen mode Exit fullscreen mode

So far, none of this is Vue specific. You could just wire this into any JS code:

import { Themes } from './themes'
let themes = new Themes();'dark');
... etc ... 
Enter fullscreen mode Exit fullscreen mode

Wiring this into our Vue app was pretty straight forward. In our main.js we just imported it, and made it a global object:

import Vue from 'vue';
import BootstrapVue from 'bootstrap-vue';
import { Themes } from './services/themes'
 // ... other imports and global config stuff 
Vue.prototype.$Themes = new Themes();
Enter fullscreen mode Exit fullscreen mode

To give the user the ability to switch themes, I added two new options in our nav bar component:

    <b-navbar toggleable="sm">
    <!-- Lots of other nav stuff removed -->
    <b-nav-item-dropdown >
        <template v-for="theme in themes">
            <b-dropdown-item class="capitalize" @click="switchTheme(theme)">{{theme}} Theme</b-dropdown-item>
    export default {
        data() {
            return {
                themes: []
        computed: {},
        mounted() {
            this.themes = this.$Themes.names;
        methods: {
            switchTheme(theme) {
Enter fullscreen mode Exit fullscreen mode

Original Light Theme:

New Dark Theme:

Top comments (0)