DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 967,611 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Dark mode easily on the web
Medhat Dawoud
Medhat Dawoud

Posted on • Updated on

Dark mode easily on the web

This post has been originally published on my blog

Dark mode πŸŒ’ has been a trend for the last few years, and you can find almost all website enable that, including famous ones like Twitter, and the main reason for that fame of Dark mode is that in low light places it is way better for man's eyes to see the light text on a dimmed background than vice versa.

In this quick article, I'm trying to show you the way you can implement that easily using CSS and JavaScript.

Assumptions

we have a small HTML page that has a light theme by default and we need to implement the dark theme as well as an option for the visitors, so basically we are going to do that easily by changing the variables of the CSS either it is the custom properties of CSS --primary-color or using Sass $primary-color or any other way.

Here is how it looks like a light theme

light theme example

Explanation

let's have a look first into the CSS variables we have (don't worry the whole code is on a GitHub repo that is mentioned at the end of the article)

:root {
  --primary-bg: #eee;
  --primary-fg: #000;
  --secondary-bg: #ddd;
  --secondary-fg: #555;
  --primary-btn-bg: #000;
  --primary-btn-fg: #fff;
  --secondary-btn-bg: #ff0000;
  --secondary-btn-fg: #ffff00;
  --image-opacity: 1;
}

// here is the rest of the CSS styles
Enter fullscreen mode Exit fullscreen mode

The main goal is to change these variables values to the following:

:root {
  --primary-bg: #282c35;
  --primary-fg: #fff;
  --secondary-bg: #1e2129;
  --secondary-fg: #aaa;
  --primary-btn-bg: #ddd;
  --primary-btn-fg: #222;
  --secondary-btn-bg: #780404;
  --secondary-btn-fg: #baba6a;
  --image-opacity: 0.85;
}
Enter fullscreen mode Exit fullscreen mode

only in case we have a dark mode preference from the user, the above variables are the same variables names with only different values to make the theme dark, as whenever you define the same variable twice the later one will override the first one.

Implementation using only CSS

We have several ways to resolve this issue, for example using prefers-color-scheme media query in CSS, will enable the list of color variables if the media query matches as follow:

@media (prefers-color-scheme: dark) {
  :root {
    --primary-bg: #282c35;
    --primary-fg: #fff;
    --secondary-bg: #1e2129;
    --secondary-fg: #aaa;
    --primary-btn-bg: #ddd;
    --primary-btn-fg: #222;
    --secondary-btn-bg: #780404;
    --secondary-btn-fg: #baba6a;
    --image-opacity: 0.85;
  }
}
Enter fullscreen mode Exit fullscreen mode

It has a great support in most of the modern browsers, and of course not IE11.

In this case, you don't have to implement a toggle button for the user as your website will follow the user preference anyway.

User preference: In modern operating systems you can change the general theme of the OS in settings to have it dark or light, and by adding the above code in your CSS it will get the user preference from the operating system and show the website in the preference of the user based on it, that's a great tip πŸ’«

Here is how it looks in dark mode:

dark mode example

But you might face a problem if the user prefers to preview your website in light mode regardless of the operating system preferences, in this case, you have to implement a button for the user to switch to their own preference.

Implementing a toggle button (JavaScript)

Let's start by adding a simple script tag in the end of the HTML file before the closing of the body, and select in it the button that we are going to use as dark mode toggle.

// here is the button
<div id="dark-mode-toggle" title="Dark mode toggle">πŸŒ’</div>
... // here is the script tag
<script>
  const toggleButton = document.querySelector("#dark-mode-toggle")
</script>
Enter fullscreen mode Exit fullscreen mode

Now we should think about a way to keep that user preference saved and persisted, and the best solution for that is localStorage.

let's listen to the click on that button and check if the value of the theme key in localStorage is dark convert it to light and change that Icon otherwise do the opposite.

Here is the script:

<script>
  const toggleButton = document.querySelector('#dark-mode-toggle');

  toggleButton.addEventListener('click', (e) => {
    darkMode = localStorage.getItem('theme');
    if (darkMode === 'dark') {
      disableDarkMode();
    } else {
      enableDarkMode();
    }
  });

  function enableDarkMode() {
    localStorage.setItem('theme', 'dark');
    toggleButton.innerHTML = 'β˜€οΈ';
  }

  function disableDarkMode() {
    localStorage.setItem('theme', 'light');
    toggleButton.innerHTML = 'πŸŒ’';
  }
</script>
Enter fullscreen mode Exit fullscreen mode

Now we have a functionality of the button to change the theme key in localStorage from light to dark and vice versa, and also it switches the icons to show something, but still, we didn't reach our goal.

The idea here is to create a wrapper class that will hold the dark mode CSS variables and adds/remove that class based on the condition, and the best element to use for that in the body.

First modify the CSS and create that class as follow:

.dark-mode {
  --primary-bg: #282c35;
  --primary-fg: #fff;
  --secondary-bg: #1e2129;
  --secondary-fg: #aaa;
  --primary-btn-bg: #ddd;
  --primary-btn-fg: #222;
  --secondary-btn-bg: #780404;
  --secondary-btn-fg: #baba6a;
  --image-opacity: 0.85;
}
Enter fullscreen mode Exit fullscreen mode

then let's move to the script to change the functions a little bit:

function enableDarkMode() {
  document.body.classList.add("dark-mode")
  localStorage.setItem("theme", "dark")
  toggleButton.innerHTML = "β˜€οΈ"
}

function disableDarkMode() {
  document.body.classList.remove("dark-mode")
  localStorage.setItem("theme", "light")
  toggleButton.innerHTML = "πŸŒ’"
}
Enter fullscreen mode Exit fullscreen mode

Now the functionality should be working properly on clicking on the toggle button as follow:

a working dark light mode toggle

One more thing to notice is that on reloading you are not getting the dark mode if it is the setting in localStorage, and the solution is pretty easy, by adding this at the beginning of the script.

let darkMode = localStorage.getItem("theme")

if (darkMode === "dark") enableDarkMode()
Enter fullscreen mode Exit fullscreen mode

That's it and you can go now, BUT in this case we lost the user preference that we implemented before using the media query, the good news is that we can listen to that in Javascript as well as follow:

window
  .matchMedia("(prefers-color-scheme: dark)")
  .addListener(e => (e.matches ? enableDarkMode() : disableDarkMode()))
Enter fullscreen mode Exit fullscreen mode

by using the above code, whenever the user change his preference your website will follow that, finally we have a complete solution, here is the full script tag:

<script>
  const toggleButton = document.querySelector("#dark-mode-toggle")
  let darkMode = localStorage.getItem("theme")

  if (darkMode === "dark") enableDarkMode()

  toggleButton.addEventListener("click", e => {
    darkMode = localStorage.getItem("theme")
    if (darkMode === "dark") {
      disableDarkMode()
    } else {
      enableDarkMode()
    }
  })

  function enableDarkMode() {
    document.body.classList.add("dark-mode")
    localStorage.setItem("theme", "dark")
    toggleButton.innerHTML = "β˜€οΈ"
  }

  function disableDarkMode() {
    document.body.classList.remove("dark-mode")
    localStorage.setItem("theme", "light")
    toggleButton.innerHTML = "πŸŒ’"
  }

  window
    .matchMedia("(prefers-color-scheme: dark)")
    .addListener(e => (e.matches ? enableDarkMode() : disableDarkMode()))
</script>
Enter fullscreen mode Exit fullscreen mode

Conslusion

πŸ˜… Phew, that was it, an easy but important solution that is very popular nowadays, you can find the whole code example on the Github repo, and I hope that you learned something new in this quick tutorials.

Feel free to share it or discuss it with me on Twitter if you want any help, or follow and let's be friends.

If you understand Arabic, here is an explanation step by step in an Arabic tutorial:
https://youtu.be/QC0PMPhq6CM

Tot ziens πŸ‘‹

Top comments (12)

Collapse
 
jvarness profile image
Jake Varness

This is much better than all those filter: invert tricks I always see. Nice article!

Collapse
 
medhatdawoud profile image
Medhat Dawoud Author

Thanks, Jake!

Collapse
 
rowemore profile image
Rowe Morehouse

Here's the easiest way to do Dark Mode β€” for the entire web!

chrome://flags/#enable-force-dark

… just enable the force-dark flag in Chrome. :)

Try it!

Collapse
 
bobbyiliev profile image
Bobby Iliev

Great article! Well done πŸ™Œ

We recently added dark mode to the devdojo.com website using TailwindCSS.

Collapse
 
medhatdawoud profile image
Medhat Dawoud Author

Your site looks really good, however, you don't apply the user preference, I believe it will be a great addition if you do ;)

Collapse
 
bobbyiliev profile image
Bobby Iliev

Nice, good point! Thanks for the suggestion πŸ‘Œ

Collapse
 
kmhmubin profile image
K M H Mubin

Great article! Well done πŸ™Œ

Collapse
 
ismlhbb profile image
Ismail Habibi Herman

This is incredible, great tutorialπŸ”₯ thankyou πŸ™

Collapse
 
huykira profile image
Huy Kira

Thanks for sharing

Collapse
 
medhatdawoud profile image
Medhat Dawoud Author

You are welcome, Huy!

Collapse
 
teslaji profile image
Kuldeep Bhatt

Good Stuff

Collapse
 
medhatdawoud profile image
Medhat Dawoud Author

Thanks, Kuldeep!

This post blew up on DEV in 2020:

js visualized

πŸš€βš™οΈ JavaScript Visualized: the JavaScript Engine

As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! πŸ₯³

Happy coding!