DEV Community

Cover image for Dark mode with 1(or few) line of CSS 🌓
Kavindu Santhusa
Kavindu Santhusa

Posted on • Updated on

Dark mode with 1(or few) line of CSS 🌓

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 */
}
Enter fullscreen mode Exit fullscreen mode

Then write your HTML.

...
<body class="dark">
  <h1>Hello World!</h1>
   ...
</body>
...
Enter fullscreen mode Exit fullscreen mode

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: ...;
}
...
...
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
...
...
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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%);
}
Enter fullscreen mode Exit fullscreen mode

Then add .dark class to the html tag. That's the perfect place to it.

<html class="dark">
   ...
</html>
Enter fullscreen mode Exit fullscreen mode

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%;
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Do the same thing for other media elements.

.dark,
.dark img,
.dark picture,
.dark video,
.dark canvas {
  filter: invert(100%) hue-rotate(180deg);
}
Enter fullscreen mode Exit fullscreen mode

Create a class named nofilter to apply when you need to apply dark theme to media elements.

.nofilter {
  filter: none !important;
}
Enter fullscreen mode Exit fullscreen mode

Then use nofilter in HTML

<img class="nofilter" src="path/to/image" alt="something"/>
Enter fullscreen mode Exit fullscreen mode

It looks smooth with some transition.

html,
img,
picture,
video,
canvas {
  transition: filter 0.3s ease-in-out;
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Button is rounded and transparent.

button {
  background: transparent;
  border: transparent;
  border-radius: 50%;
  cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

there is a JavaScript function to toggle dark mode.

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

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); }
Enter fullscreen mode Exit fullscreen mode

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

Discussion (2)

Collapse
maybenoobish profile image
Matthias Kluth

Love that solution. Thanks for sharing, man.

Collapse
ksengine profile image
Kavindu Santhusa Author

Welcome 🤗.