CSS Dark Mode

Excerpt

The "dark mode" feature is still broken - but you can fix it.

This Calendar door has a long article and was created from a Proof of Concept (POC) implementation from my IoT Open Source project where I need a web frontend implementation for ESP8266 based devices that fits into 1MByte available flash ROM. See my comments at the end of the article.

Brief intro

In the operating systems Windows, iOS, Android and more the dark-mode feature has been implemented to give the user the option to adapt the applications to their preferences. Actually on LED displays the power consumption is reduced when using a dark background instead of a white one.

The web browser support this out of the box when you add the following <meta> tag to the page:

<meta name="color-scheme" content="light dark">

or in CSS

:root {
  color-scheme: dark light;
}

This tells the browser that your page supports the two color-scheme modes light and dark and that light is preferred.

As long as you do not have fixed colors in your CSS that's all you have to do and it works well. That is what you see on this side.

Switch on the page

The common known switch for the color-scheme is embedded in the operating system or the browser settings. By using the document.documentElement.style.colorScheme property you can change the color-scheme mode on the page. Here is a short script for demonstration that is also included in this page:

<button onclick="javascript:switchMode('dark light')">default</button>
  <button onclick="javascript:switchMode('light')">light</button>
  <button onclick="javascript:switchMode('dark')">dark</button>

  <script>
    function switchMode(colScheme) {
      document.documentElement.style.colorScheme = colScheme;
    };
  </script>

You can see and try the switches in action located fixed at the top of the window.

Define fixed colors in CSS

This approach is quiet simple and can be used even with inline styling rules as we choose colors that fit into the light and the dark mode like the warning above. The Warning box below will stay in the defined colors but actually works in both modes.

.simple-warning-box {
  background-color:bisque;
  color:darkred;
}
Problem: Only the Operating system / Browser settings based color-scheme information is available to CSS. It is not possible (out of the box) to define CSS rules based on the effective color-scheme.

Define adaptive colors in CSS (no switch)

This approach is is based on the fact that the @media rules can be used to show different colors dependant on the user preferences in the OS / Browser settings. Here we can ask for

@media (prefers-color-scheme: dark) {
  .noswitch-warning-box {
    background-color: darkred;
    color: bisque;
  }
}

@media (prefers-color-scheme: light) {
  .noswitch-warning-box {
    background-color: bisque;
    color: darkred;
  }
}

Problem:Only the Operating system / Browser settings based color-scheme information is available to CSS. It is not possible (out of the box) to define CSS rules based on the effective color-scheme.

When you switch the mode in the operating system you will see that the CSS rules apply correctly but when you use the switches on top of the page - they are not reflected.

This is the broken link. The CSS rules available out-of the box do not fit to the also available API through document.documentElement.style.colorScheme.

Define adaptive colors in CSS (switchable)

There are workarounds for this and some descriptions and implementations in the wild can been found. This cannot be implemmented with inline CSS because there we cannot define rules with @media or root-class dependencies. This also needs some JavaScript to capture events and set a class or attribute on a root element.

I use the class name 'dark-mode' here I apply to the root element when dark-mode is wanted. This doesn't work without JavaScript as of now so here is the code for this.

// called on page load, by the switch buttons and on prefers-color-scheme changes.
function switchMode(colScheme) {
  if (colScheme === 'light') {
    document.documentElement.style.colorScheme = 'light';
    document.documentElement.classList.remove('dark-mode');
    document.documentElement.classList.add('light-mode');
  } else if (colScheme === 'dark') {
    document.documentElement.style.colorScheme = 'dark';
    document.documentElement.classList.remove('light-mode');
    document.documentElement.classList.add('dark-mode');
  } else {
    document.documentElement.style.colorScheme = 'dark light';
    const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
    document.documentElement.classList.remove('dark-mode');
    document.documentElement.classList.remove('light-mode');
  }
};

// register events to switchMode
window.addEventListener("load", switchMode);
window.matchMedia("(prefers-color-scheme: dark)").addListener(switchMode);
.switch-warning-box {
  background-color: bisque;
  color: darkred;
}

:root.dark-mode .switch-warning-box {
  background-color: darkred;
  color: bisque;
}
Problem: Only the Operating system / Browser settings based color-scheme information is available to CSS. It is not possible (out of the box) to define CSS rules based on the effective color-scheme.

Press + or + to view the complete source code of this page implementation.

My Comments

My personal decission was not to implement a switch on the page level but to use the built-in dark mode using the CSS rule :root { color-scheme: dark light; } because a switch element creates more complexity than it adds value. Here is a PoC if you need it.

As of implementing this PoC I found the trick to capture the preferred mode and save it into a classname to make it available to CSS rules something the browser should have done automatically.

I believe that things may be implemented smarter than this one and I suggest to have a selector rule like [color-scheme='dark'] that corresponds to setting document.documentElement.style.colorScheme or maybe document.documentElement.colorScheme and inheriting it to child objects. Also setting manually the mode on a object level by overwriting the inherited one like <div style="color-scheme:dark"> is a wish.

See also

Tags

CSS JavaScript