Dark Mode switching

Posted By Will Moody On , Reading Time - 5 minutes

Snippet

Dark Mode Swithing

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 accomodating 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');
    }
  }
}