Fat Buddha Designs

Affordable, Creative, Hand-Crafted Websites

Dark Mode switching

Dark Mode Swithing

Author: Will Moody

Published:

Reading Time: (approx) 3 min

Tags that this post has been filed under.

We all have our own preferences and that includes how we use colors when browsing the internet.

We can now switch between light and dark mode thus accommodating peoples preferences.

To do this using Gridsome was very easy as the Gridsome website itself has this function, so we can utilise some of their code.

Firstly if you don't have one create an index.html file in your src directory and add this code:-

 <body id="top" role="application" ${bodyAttrs}>
  <script>
       // Add dark / light detection that runs before Vue.js load. Borrowed from overreacted.io
       (function() {
       window.__onThemeChange = function() {};
       function setTheme(newTheme) {
       window.__theme = newTheme;
       preferredTheme = newTheme;
       document.body.setAttribute('data-theme', newTheme);
       window.__onThemeChange(newTheme);
       }

       var preferredTheme;
         try {
          preferredTheme = localStorage.getItem('theme');
         } catch (err) { }

         window.__setPreferredTheme = function(newTheme) {
           setTheme(newTheme);
            try {
             localStorage.setItem('theme', newTheme);
            } catch (err) {}
          }

          var darkQuery = window.matchMedia('(prefers-color-scheme: dark)');

          darkQuery.addListener(function(e) {
            window.__setPreferredTheme(e.matches ? 'dark' : 'light');
          });

          setTheme(preferredTheme || (darkQuery.matches ? 'dark' : 'light'));
        })();
      </script>

    ${app}
    ${scripts}
 </body>

Then create a partial called ToggleTheme.Vue and add this code:-

<template>
    <a role="button" @click.prevent="toggleTheme()" :aria-label="'Toggle ' + nextTheme" :title="'Toggle ' + nextTheme"
        class="toggle-theme">
        <span v-for="theme in themes" :key="theme" :data-theme="theme" />

        <svg v-if="current === 'light'" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
            fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
            class="feather feather-sun">
            <circle cx="12" cy="12" r="5"></circle>
            <line x1="12" y1="1" x2="12" y2="3"></line>
            <line x1="12" y1="21" x2="12" y2="23"></line>
            <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
            <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
            <line x1="1" y1="12" x2="3" y2="12"></line>
            <line x1="21" y1="12" x2="23" y2="12"></line>
            <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
            <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
        </svg>

        <svg v-else-if="current === 'dark'" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
            viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
            stroke-linejoin="round" class="feather feather-moon">
            <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
        </svg>

        <svg v-else-if="current === 'sepia'" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
            viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
            stroke-linejoin="round" class="feather feather-coffee">
            <path d="M18 8h1a4 4 0 0 1 0 8h-1"></path>
            <path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"></path>
            <line x1="6" y1="1" x2="6" y2="4"></line>
            <line x1="10" y1="1" x2="10" y2="4"></line>
            <line x1="14" y1="1" x2="14" y2="4"></line>
        </svg>
    </a>
</template>

<script>
    export default {
        data() {
            return {
                themes: ['light', 'dark', 'sepia'],
                current: 'light',
            }
        },
        computed: {
            nextTheme() {
                const currentIndex = this.themes.indexOf(this.current)
                const nextIndex = (currentIndex + 1) % this.themes.length
                return this.themes[nextIndex]
            },
        },
        methods: {
            toggleTheme() {
                const currentIndex = this.themes.indexOf(this.current)
                const nextIndex = (currentIndex + 1) % this.themes.length
                window.__setPreferredTheme(this.themes[nextIndex])
                this.current = this.themes[nextIndex]
            },
        },
        mounted() {
            if (window.__theme) {
                this.current = window.__theme
            }
        },
    }
</script>

<style lang="scss">.toggle-theme {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 9999;
    color: var(--f-color);
    background-color: var(--bkg-color);
    padding: 0.4rem;
    border: none;
    cursor: pointer;

    svg {
        fill: var(--f-color);
    }

    &:hover {
        opacity: 0.8;
    }

    &:focus {
        outline: none;
    }
}

</style>

You can then call this partial in your Default.vue like this:-

<!-- Default Layout -->
    <template>
      <div id="app">
        <ToggleTheme />
        <Nav />
        <Header />

        <main id="main" role="main" tabindex="-1">
          <!-- /main -->
          <slot />
        </main>
        <!-- /main -->

        <Footer />
      </div>
</template>

<script>
    import ToggleTheme from '~/layouts/partials/ToggleTheme.vue'
    import Nav from '~/layouts/partials/Nav.vue'
    import Header from '~/layouts/partials/Header.vue'
    import Footer from '~/layouts/partials/Footer.vue'

    export default {
      components: {
        ToggleTheme,
        Nav,
        Header,
        Footer,
      },
    }
 </script>

And then in your Default.vue you can add something like this to your Scss

@media not all and (prefers-color-scheme: dark) {
    :root {
        --f-color: var(--color-white);
        --bkg-color: var(--color-grey-11);
    }
}

@media not all and (prefers-color-scheme: light) {
    :root {
        --f-color: var(--color-grey-8);
        --bkg-color: var(--color-white);
    }
}

[data-theme*='dark'] {
    --f-color: var(--color-white);
    --bkg-color: var(--color-grey-11);
}

[data-theme*='light'] {
    --f-color: var(--color-grey-8);
    --bkg-color: var(--color-white);
}

And then use those values throughout your pages, partials, templates and components.

This will give you a choice of a light or dark colour scheme.

I have even used this method to change a logo from a light to dark version by using a background-image and changing the URL for the light or dark version. See the example below.

[data-theme*='light'] {
  header {
    .logo--holder {
      background-image: url('~@/assets/img/Fat-Buddha-Designs-logo.png');
    }
  }
}

[data-theme*='dark'] {
  header {
    .logo--holder {
      background-image: url('~@/assets/img/Fat-Buddha-Designs-logo-light.png');
    }
  }
}

Previous Article: Debugging website code with CSS

Next Article: What Type Of Website Do You Need?