Dark and light mode with light-dark()

10.12.2024

As you know, the main purpose of your own website is to try out new things. Dark and light modes are of course not new, but so far I haven't had the pleasure of trying out the light-dark() CSS function. I will do so now.

To enable support for the light-dark() color function, the color-scheme must have a value of light dark, usually set on the :root pseudo-class.

Alright, so let's start with some CSS …

:root {
  color-scheme: light dark;
}

Instead of setting a single value for color or background-color we can now use the light-dark() function and define one color for each mode. As defined in the :root Element, the first one is for light and the second one for dark mode. Of course you can use custom properties as well, but to keep the example more readable, let's stick to hex codes.

body {
  background-color: light-dark(#fff, #000);
  color: light-dark(#000, #fff);
}

That's it?!

Now we have a working light and dark mode, based on the system preferences of the user. But if we want the user to switch between modes, we have to go some extra steps.

At first we need some buttons which can be used to change the between light and dark mode. I also add one to return back to system default. Each button has a js- class, which we will use to identify the buttons with JavaScript. In addition we add a data-attribute which holds the value of the selected mode.

<button class="js-color-scheme-button" data-color-scheme="light">Light Mode</button>
<button class="js-color-scheme-button" data-color-scheme="dark">Dark Mode</button>
<button class="js-color-scheme-button" data-color-scheme="">System Default</button>
document.addEventListener('DOMContentLoaded', (event) => {
  // find the all the buttons based on the js- class
  const colorSchemeButtons = document.querySelectorAll('.js-color-scheme-button');
  // get previously selected color scheme from localStorage
  const colorScheme = localStorage.getItem('colorScheme') || '';
  // find active button based on color scheme
  const activeButton = document.querySelector('.js-color-scheme-button[data-color-scheme="' + colorScheme + '"]');

  if (activeButton) {
    // add css class to active button to apply different styles via css
    activeButton.classList.add('is-active');
  }

  if (colorSchemeButtons) {
    // do the same thing for all buttons
    colorSchemeButtons.forEach(button => {
      // listen to the click event (which includes touch as well)
      button.addEventListener('click', event => {
        // get the mode value from the data attribute
        const colorScheme = button.dataset.colorScheme ?? '';
        // remove active class from every button
        colorSchemeButtons.forEach(btn => {
          btn.classList.remove('is-active');
        })
        // and add it to the selected one
        button.classList.add('is-active');
        // add selected color scheme it to the html element
        document.documentElement.setAttribute('data-color-scheme', colorScheme);
        // save the value to local storage to keep the selection on page reload
        localStorage.setItem('colorScheme', colorScheme);
      })
    })
  }
})

To activate the different CSS styles based on the selected color scheme, we have to apply it based on the data attribute of the html element, which we just changed via JavaScript. Now the browser knows which mode is set and which of the two values from the light-dark() method is to be used.

/* force light mode, if activated by user (via JavaScript) */
html[data-color-scheme="light"] {
    color-scheme: light;
}

/* force dark mode, if activated by user (via JavaScript) */
html[data-color-scheme="dark"] {
    color-scheme: dark;
}

Flickering colors?

If you put your Javascript at the end of your HTML document you might notice flickering colors as soon as the selected value in localStorage differs from your system color scheme. This is because the website gets loaded with your system preference and in the end the JavaScript kicks in and changes all the colors. To prevent this, you can add two additional lines of JavaScript to the <head> section of your page. This changes the data-attribute to the one saved to localStorage bevor the CSS is applied.

<!-- additional script for <head> section to prevent flickering -->
<script>
  const colorScheme = localStorage.getItem('colorScheme') || '';
  document.documentElement.setAttribute('data-color-scheme', colorScheme);
</script>

Sources


PHP HTML CSS JS

What do you think?

Let's discuss on Mastodon