DEV Community

Cover image for 🌙 How to implement darkmode with a Vue.js component

🌙 How to implement darkmode with a Vue.js component

tq-bit on June 27, 2021

Implementing darkmode in your webapp will be sugar for your nightowl readers. It implements a high-contrast color scheme that's soothing for the ey...
Collapse
 
asparoth profile image
NoobDev

Hi there!
When I move pages, the button always has the toggle animation (even though the correct theme is active) when I enable dark mode.
So if I go from one page to the other, the toggle will initially move again while keeping the theme intact.

Any ideas? I went over your code and it looks the exact same for the script part.

Collapse
 
tqbit profile image
tq-bit

Are you using the component within a router-view? It's possible the animation gets triggered whenever the component re-renders.

mounted() {
  const initUserTheme = this.getTheme() || this.getMediaPreference();
  this.setTheme(initUserTheme);
}
Enter fullscreen mode Exit fullscreen mode

You could try and place the component outside of <vue-router />.

Alternatively, you could try to abstract the CSS positioning into a separate class & dynamically apply it to the template:

<label for="checkbox" class="switch-label">
  <span>🌙</span>
  <span>☀️</span>
  <div
    class="switch-toggle"
    :class="{ 
        'switch-toggle-checked': userTheme === 'dark-theme', 
        'switch-toggle-unchecked': userTheme === 'light-theme'
     }"
  ></div>
</label>
Enter fullscreen mode Exit fullscreen mode

If you can, please share your source code and I'll have a closer look :-)

Collapse
 
asparoth profile image
NoobDev • Edited

So what I found out is that the transition css element should only be triggered on a click and now it triggers on every load.
The second solution didn't do it for me either.
My component is not being triggered within the vue router, but i found a different solution which is a little bit hacky:

.switch-toggle-checked {
transform: translateX(calc(var(--element-size) * 0.6)) !important;
transition: none;
}

This only shows the transition animation if you're in light mode.

Thank you a lot for this tutorial! It helped me a lot

Collapse
 
violacase profile image
violacase • Edited

Odd that I am the first one to react here. For this article is GREAT!
And above all: All steps work till the very end. Congrats.

Collapse
 
violacase profile image
violacase • Edited

That being said... getMediaPreference() is not a good idea for lots of reasons.
Simply replace it with a get from localStorage with f.i.:

getTheme() {
this.setTheme(localStorage.getItem("user-theme"))
},

Collapse
 
tqbit profile image
tq-bit

Thank you for your reply. & you got a valid point. I'll add your suggestion here & in the code sandbox. Will keep the getMediaPreference() as a default tho

Thread Thread
 
violacase profile image
violacase

Why keep it as a default? It really is BAD. My solution is SIMPLE, easy to implement and last but not least: secure and without any issues on all kind of browsers. My 2 pennies.

Thread Thread
 
tqbit profile image
tq-bit

With default, I meant as much as 'if there's no previous user preference in localStorage, use the result from getUserPreference. Please pick me up though on what's bad about reading out "(prefers-color-scheme: dark)" (besides missing support for IE11). I've seen it in other implementations and never had any issues using it.

Thread Thread
 
violacase profile image
violacase • Edited

Oké. I was in error. Nothing wrong with getUserPreference. Thanks and good luck with all your work.:-)

BTW: for just toggling between two color themes you can also do it with plain CSS. See developer.mozilla.org/en-US/docs/W...

Thread Thread
 
tqbit profile image
tq-bit

Mh, how would you do this? prefers-color-scheme is a read-only attribute in the browser's context.

In another project (w/o Vue), I'm using a different approach with SASS though, creating a function & a few mixins to apply themes

In a styles/state.scss file:

:root {
  --light-bg-color: #{$color-light-white};
  --light-text-color: #{$color-dark-grey};
  --bg-color: #{$color-white};
  --text-color: #{$color-black};
  --dark-bg-color: #{$color-light-white};
  --dark-text-color: #{$color-dark-grey};
}

:root.dark-theme {
  --light-bg-color: #{$color-dark-grey};
  --light-text-color: #{$color-light-white};
  --bg-color: #{$color-grey};
  --text-color: #{$color-white};
  --dark-bg-color: #{$color-dark-grey};
  --dark-text-color: #{$color-white};
}

@function theme-color($for) {
  @return var(--#{$for}-color);
}

@mixin theme-colors {
  background-color: theme-color('bg');
  color: theme-color('text');
  fill: theme-color('text');
}

@mixin light-theme-colors {
  background-color: theme-color('light-bg');
  color: theme-color('light-text');
  fill: theme-color('light-text');
}

@mixin dark-theme-colors {
  background-color: theme-color('dark-bg');
  color: theme-color('dark-text');
  fill: theme-color('dark-text');
}
Enter fullscreen mode Exit fullscreen mode

To apply it to the whole HTML - site: in styles/index.scss:

@import './state.scss';
html {
  @include dark-theme-colors;
}
Enter fullscreen mode Exit fullscreen mode

The JS code is quite similar to the one in the article as well

Thread Thread
 
violacase profile image
violacase

Right now I'm working on a multi themes setup. I'll come back to you later when a demo project has been finished. Will take me a couple of days (in my spare time).