paint-brush
Add Dark Mode to Your Web Page With One Line of CSS 🌓by@ksengine
1,564 reads
1,564 reads

Add Dark Mode to Your Web Page With One Line of CSS 🌓

by Kavindu SanthusaDecember 1st, 2021
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

I wanted to add dark mode to my blog(currently in development) So I searched and searched for a simple solution. Instead of using JavaScript anyone can use `mix-blend-mode` in CSS to enable dark theme. The following example explains how it works using CSS filters. It converts the color of links from yellow to yellow to odd color. It's complex and have some issues, but I am a developer(as a hobby). Not a designer. I am going to introduce my own idea. But later I found same 2 articles.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Add Dark Mode to Your Web Page With One Line of CSS 🌓
Kavindu Santhusa HackerNoon profile picture

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:


  • Light-on-dark
  • Black mode
  • Dark mode
  • Dark theme
  • Night mode


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

Gif of flying Superman 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!

A man got an idea and someone hits him using a glass bottle

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

Final Example

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

Final Demo

This is an advanced demo.

https://codepen.io/ksenginew/pen/gOxVzXM


First published here