Implement Dark Mode with One Line of CSS

with filter: invert(1), which is not ideal, but it can be a starting point

Yihui Xie 2023-09-25

Personally I do not like the dark mode. I love sharp full-black text on clean white background, which gives me the same feeling as reading on paper. Reading in the dark mode makes me feel lost in the dark, but I know many people are the opposite, therefore in this crowded world of numerous dichotomous choices, web developers and authors have one more decision to make: should the website support the dark mode?

To support the dark mode well from the scratch is not a trivial task. You may have to tweak a lot of colors, depending on how many colors you use on your webiste. That is one of the reasons why I have almost stopped using colors other than black, white, and gray after some years of wrestling with colors. Eventually I felt my decision fatigue outweighed the website appearance. One exception for now is syntax highlighting for code blocks, which I might also give up someday.

Of course, you can use a CSS framework, and there exist many, such as Bootstrap. They are extremely flexible, which means you can make a great many decisions if you would like to, e.g., what color --bs-info-border-subtle or $purple-600 should really be. Bear in mind that we use a tool to save our time. I, for one, often get hooked on customizing things if I’m offered the chances.

Invert all colors

To get started with the dark mode from the light mode can be simple and quick, though. You can invert the colors of everything with a single CSS filter:

html {
  filter: invert(1);
}

That is, black will become white, and white will become black. Generally, for a color rgb(x, y, z), the fully inverted color will be rgb(255 - x, 255 - y, 255 - z), where x, y, and z range from 0 to 255.

Another simple starting point can be color: white; background-color: black;, but it only works for text and not other elements such as borders.

Set an opaque background color first

There is one caveat: if the background color of the page is transparent (which is the default), it will not be inverted, because transparent white and transparent black are identical. The page will not look good. In this case, you have to set an opaque light background in the light mode, e.g.,

html {
  filter: invert(1);
}
body {
  background: white;
}

You can try this on any web page in the “Styles” pane of the Developer Tools of your web browser, and you are likely to find the page look funny, especially the images. Yes, even colors on images will be inverted, which will make an image look like the film of a photograph, e.g.,

An inverted logo

To fix that problem, you can invert visual elements like images one more time to restore their colors:

html, img, video, iframe {
  filter: invert(1);
}

If your page only uses black, white, and gray, this “dark mode” should be good enough. If not, it is simple enough to tweak.

All colors are opposite now

With this invert(1) filter applied to the top-level <html> tag, you need to think colors in the opposite way for all elements. For example, if you do not want the body background to be fully black, you need to set it to lightgray, e.g.,

body {
  background-color: rgb(230, 230, 230);  /* or #e6e6e6 */
}

That will be inverted to darkgray.

Automatic dark mode

If you want automatic (adaptive) dark mode when the system uses the dark mode, you can use the media query:

@media (prefers-color-scheme: dark) {
  html, img, video, iframe {
    filter: invert(1);
  }
  body {
    background-color: white;
  }
}

Do you want to give readers a choice to manually specify whether they want the dark mode or not? That is yet another decision to make. In the end, we lament that “Time flies!” But time never flies. Desire does.