I wanted to add dark mode to my blog(currently in development). So I searched and searched and searched for a simple solution.
I am going to introduce my own idea. I later found 2 articles that gave some pointers but their solutions are somewhat incomplete.
The solution is CSS filters!
Dark mode has too many names:
There is a silly way to do this.
.dark {
background-color: black; /* from white to black */
color: white; /* from black to white */
}
Then write your HTML.
...
<body class="dark">
<h1>Hello World!</h1>
...
</body>
...
This works finely for examples. In production websites, there are too many styled items like buttons, alerts, modals etc. When you try to style them all for dark mode, you end up with something like:
body {
background-color: ...;
color: ...;
}
.btn {
background-color: ...;
color: ...;
}
.btn.primary {
background-color: ...;
color: ...;
}
...
...
.dark {
background-color: ...;
color: ...;
}
.dark .btn {
background-color: ...;
color: ...;
}
.dark .btn.primary {
background-color: ...;
color: ...;
}
...
...
Doing this doubles the size of your CSS Stylesheet.
What is the solution to reduce the size of the stylesheet???
Here comes the Superman of CSS. CSS variables(CSS custom properties)!!!
Define theme colors as CSS variables.
:root {
--text-color: black;
--bg-color: white;
}
.dark {
--text-color: white;
--bg-color: black;
}
Then use variables to set colors
body, .dark {
background-color: var(--bg-color);
color: var(--text-color);
}
.btn {
background-color: var(--bg-color);
color: var(--text-color);
}
.btn.primary {
background-color: var(--bg-color);
color: var(--text-color);
}
...
...
This works fine.
But I am a developer(as a hobby). Not a designer. Which colors should I use in light theme and dark theme?.
That’s when I found Darkmode.js. It uses the CSS mix-blend-mode: difference;
to enable dark mode for web pages. It's complex and has some issues. Instead of using JavaScript, anyone can use mix-blend-mode
in CSS to enable dark theme. The following example explains how mix-blend-mode: difference;
works.
light-theme-color = rgb(x, y, z)
dark-theme-color = rgb(255-x, 255-y, 255-z)
It converts the color of links from blue to odd yellow color.
light-theme-color = rgb(0, 0, 238) = blue
dark-theme-color = rgb(255-0, 255-0, 255-238) = rgb(255, 255, 17) = yellow
After a few days of tinkering, I got an idea. Queue CSS filters!
Use invert filter to enable dark mode.
.dark {
filter: invert(100%);
}
Then add a .dark
class to the html
tag.
<html class="dark">
...
</html>
This method won't work without text color and background color. Set height
to 100%
on the html
and body
tags.
html,
body {
color: #222;
background-color: #fff;
height: 100%;
}
invert(100%)
is same as mix-blend-mode
. But simpler.
light-theme-color = rgb(x, y, z)
dark-theme-color = rgb(255-x, 255-y, 255-z)
A downside to this method is that it has the same issue with links being yellow in dark mode. To fix this we should do something like this
.dark {
filter: invert(100%) hue-rotate(180deg);
}
hue-rotate(180deg)
changes the color on the hue wheel to the opposite color on the hue wheel. Here is the hue wheel.
The following example explains how filter: invert(100%) hue-rotate(180deg);
works for link color.
light-theme-color = rgb(0, 0, 238) = blue
inverted-color = rgb(255-0, 255-0, 255-238) = rgb(255, 255, 17) = yellow = hsl(60, 100%, 53%)
hue-rotated-color = hsl(270, 100%, 53%) = light-blue
This filter is applied to images too. So images look ugly. To remove that filter on images, use the filter again. So, the same filter applied to the image twice. inverse(100%) X inverse(100%) = inverse(0)
and hue-rotate(180deg) X hue-rotate(180deg) = hue-rotate(0)
.
.dark,
.dark img {
filter: invert(100%) hue-rotate(180deg);
}
Do the same thing for other media elements.
.dark,
.dark img,
.dark picture,
.dark video,
.dark canvas {
filter: invert(100%) hue-rotate(180deg);
}
Create a class named nofilter
to apply when you need to apply dark theme to media elements.
.nofilter {
filter: none !important;
}
Then use nofilter
in HTML
<img class="nofilter" src="path/to/image" alt="something"/>
It looks smooth with some transition
.
html,
img,
picture,
video,
canvas {
transition: filter 0.3s ease-in-out;
}
How can I know the preference of the user?. There is a CSS media query to do this.
@media (prefers-color-scheme: dark) {
html,
img,
picture,
video,
canvas {
filter: invert(100%) hue-rotate(180deg);
}
}
I want to create a dark mode toggle button and store user preference or automatically detect user preference. localstorage
is perfect for storing preferences. Use window.matchMedia
to check if the current theme matches the user’s preference.
if (
localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
This is my toggle button, with an SVG
<button onclick="toggleDark()">
<svg aria-hidden="true" data-prefix="fas" data-icon="moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="height: 2rem; width: 2rem;" class="svg-inline--fa fa-moon">
<path fill="currentColor" d="M32 256C32 132.2 132.3 32 255.8 32c11.36 0 29.7 1.668 40.9 3.746 9.616 1.777 11.75 14.63 3.279 19.44C245 86.5 211.2 144.6 211.2 207.8c0 109.7 99.71 193 208.3 172.3 9.561-1.805 16.28 9.324 10.11 16.95C387.9 448.6 324.8 480 255.8 480 132.1 480 32 379.6 32 256z"></path>
</svg>
</button>
The button is rounded and transparent.
button {
background: transparent;
border: transparent;
border-radius: 50%;
cursor: pointer;
}
And finally, a JavaScript function to toggle dark mode.
let toggleDark = () => {
let result = document.documentElement.classList.toggle("dark");
localStorage.theme = result ? "dark" : "light";
};
Now, I am going to introduce a minimal subset of the above code as the tricky one-liner. This code is enough for many sites.
.dark, .dark img { filter: invert(100%) hue-rotate(180deg); }
This is an advanced demo.
https://codepen.io/ksenginew/pen/gOxVzXM
First published here