A Better Approach to Dark Mode on Your Website

Learn how to implement Dark Mode on your Website

Feature Image

Dark Mode seems to be here to stay. Initially, manly used by developers in their code editors, but now it’s all over the place, from mobile, desktops, and now even the web. Supporting Dark Mode is more and more turning from a nice to have, to a must-have. Of course, depending on your niche, but if you are here, chances are, you want to implement Dark Mode on your site or for your clients.

When it comes to dark mode for websites there are multiple approaches on the web, and I’d like to talk with you about 2 different approaches, with one being the easiest to implement, and the second being my preferred option, and the option that I actually used to implement Dark Mode on livecodestream.dev .

Let’s get started with the first option.


Dark Mode in a few lines of code

The first option works by the use of “dark” magic :p, joke aside, it’s very simple and elegant, and consists of really one line of CSS.

html[data-theme='dark'] {
    background: #000;
    filter: invert(1) hue-rotate(180deg)
}

What? You are probably wondering how that could work, and you may be right to be skeptical, but it does indeed. Let’s see it in action before we talk about why I don’t like this option so much:

As can be seen in the example it works really well, but with an issue, it is also inverting the images. And that is because the filter property additionally applies to images, background, and borders. Now, this can be fixed easily by avoiding the change on images (or better said, by inverting the inversion?):

html[data-theme='dark'] {
    background: #000;
    filter: invert(1) hue-rotate(180deg)
}

html[data-theme='dark'] img {
  filter: invert(1) hue-rotate(180deg)
}

Nice! but everything transitions so abrupt, let’s make it nicer with some nice transitions and some colors here and there:

html {
    transition: color 300ms, background-color 300ms;
}

html[data-theme='dark'] {
    background: #000;
    filter: invert(1) hue-rotate(180deg)
}

html[data-theme='dark'] img {
  filter: invert(1) hue-rotate(180deg)
}

Beautiful! It just works. Now, not always will look great, in some cases, it may look a bit strange, and we may want to have more control over each color.


Dark Mode with CSS variables

This is the technique I use on my website and works pretty well. CSS allows the definition of custom properties (variables), which are entities that can be defined in CSS with specific values and reused throughout the website. You can find detailed information on the MDN docs , however, we will quickly review here how to create a variable and how to use it.

:root {
    --my-var: blue;
}

element {
    color: var(--my-var);
}

Very simple, to name a variable we need to prepend -- to the variable name, and to use it, we make use of the function var().

Now let’s go back to our scenario and try to implement dark mode this way.

html {
  --background-primary: #fff;
  --color-primary: #333;
}

html[data-theme='dark'] {
  --background-primary: #030303;
  --color-primary: #ccc;
}

html {
  transition: color 300ms, background-color 300ms;
  background: var(--background-primary);
  color: var(--color-primary);
}

h1, h2, p {
  color: var(--color-primary);
}

It is much more cumbersome, now we need to assign to each element we use on our site the colors we want for text and background, otherwise, it won’t work. But gives you full control over what’s happening, and you can create different variables for titles, buttons, callouts, you name it.

Let’s see this on a live example:

Now, that’s a Dark Mode I love. So far we have explained the CSS side of things, but we will also work on the JavaScript code to make the switch and even save it on local storage, so the next time the user enters the site we remember his preference.


Theme toggle logic

Let’s now work on the JS that makes the switch possible. It is actually, again, very simple:

const htmlEl = document.getElementsByTagName('html')[0];

const toggleTheme = (theme) => {
    htmlEl.dataset.theme = theme;
}

What we did so far? The way the theme works is by applying a data attribute over the HTML element, for that we need to capture the element in the variable htmlEl, and then finally we have a function that receives a theme name and changes the attribute.

But we also want to make sure our changes are stored on the browser so that we remember the user preference for the next time he/she visits our site. For that we will use the localStorage API.

// Capture the current theme from local storage and adjust the page to use the current theme.
const htmlEl = document.getElementsByTagName('html')[0];
const currentTheme = localStorage.getItem('theme') ? localStorage.getItem('theme') : null;

if (currentTheme) {
    htmlEl.dataset.theme = currentTheme;
}

// When the user changes the theme, we need to save the new value on local storage
const toggleTheme = (theme) => {
    htmlEl.dataset.theme = theme;
    localStorage.setItem('theme', theme);
}

Done! Let’s see it live:


Conclusion

Today we discussed 2 options for enabling Dark Mode, however, there are more options from people very creative on how to achieve these results. If you have what you consider is a better solution, please ping me, and I’ll add it to the post to share it with others (with proper reference to the author).

I hope you learned something new today, and thanks for reading!

Join the Free Newsletter

A free, weekly e-mail with the best new articles, courses, and special bonuses.

We won't send you spam. Unsubscribe at any time.