Dark mode with 1(or few) line of CSS ๐ŸŒ“

ยท

5 min read

In this week 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. But later I found same 2 articles. But their solutions are somewhat incomplete.

It's CSS filters

Jump to final example Jump to final demo

Dark mode has too many names,

  • Light-on-dark
  • black mode
  • dark mode
  • dark theme
  • night mode

Dark mode is trending from 2018-2019.

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 real website there are too many styled items like buttons, alerts, models etc. When you styled all of them for dark mode,

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

Then you doubled your CSS stylesheet.

What is the solution to reduce the size of 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 finely.

But I am a developer(as a hobby). Not a designer. Which colors should I use in light theme and dark theme?.

Then I found Darkmode.js. It's using CSS mix-blend-mode: difference; to enable dark mode for whole web page. It's complex and have 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 few days, I got an idea... 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 .dark class to the html tag. That's the perfect place to it.

<html class="dark">
   ...
</html>

This method won't work without text color and background color. And set height to 100% on html and body tags for a whole page dark theme in contentless pages.

html,
body {
  color: #222;
  background-color: #fff;
  height: 100%;
}

invert(100%) is same as mix-blend-mode. But more simpler.

light-theme-color = rgb(x, y, z)
dark-theme-color = rgb(255-x, 255-y, 255-z)

But this method has the same issue. links are 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 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 looks ugly. To remove that filter on images, use the filter again. So 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 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 to store preference. and use window.matchMedia to check if CSS media query is matching to current state.

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

Button is rounded and transparent.

button {
  background: transparent;
  border: transparent;
  border-radius: 50%;
  cursor: pointer;
}

there is a JavaScript function to toggle dark mode.

let toggleDark = () => {
  let result = document.documentElement.classList.toggle("dark");
  localStorage.theme = result ? "dark" : "light";
};

final example

Then I am going to introduce minimal subset of 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.

I would like to know your thoughts. Maybe I can create a darkmode library.

Enjoy these articles. Follow me for more articles. Thanks ๐Ÿ’–๐Ÿ’–๐Ÿ’–

ย