Dark mode easily on web

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
...

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;
}

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;
  }
}

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>

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>

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;
}

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 = "πŸŒ’"
}

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()

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()))

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>

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 πŸ‘‹