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
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:
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:
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 π
Top comments (12)
This is much better than all those
filter: invert
tricks I always see. Nice article!Thanks, Jake!
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!
Great article! Well done π
We recently added dark mode to the devdojo.com website using TailwindCSS.
Your site looks really good, however, you don't apply the user preference, I believe it will be a great addition if you do ;)
Nice, good point! Thanks for the suggestion π
Great article! Well done π
This is incredible, great tutorialπ₯ thankyou π
Thanks for sharing
You are welcome, Huy!
Good Stuff
Thanks, Kuldeep!