Blog

Lights on or off?

Experimenting with 'dark mode' and different ways to control the theme of your website

In projects like Quantum Inspire we’ve built dark and light themes. Quantum Inspire has a dark theme editor. The docs contain the same code snippet and graph components as the editor, but now they are in a light theme:

Quantum Inspire editor in dark theme and Quantum Inspire docs in light theme

In these projects we, as designers and developers, controlled which pages get which theme. But how can we make this contextual and give this control to our users? In this article we’ll be experimenting with manual controls, geolocation, a new CSS feature query and the Ambient Light Sensor API.

For our experiment we built multiple Dark Mode demos, each based on a different technique. We want to explore their possibilities and limitations. This helps us choose the right implementation of theming in future projects. The demos can be found here.

Using manual controls

Let’s start with manual controls. It’s one of the more common approaches to themes. All we need to do to get this to work is to toggle a class. Your styling could look something like: body.theme--dark { color: #ffffff } and body.theme--light { color: #222222 }. Using CSS custom properties we can make this even more powerful.

For our experiment, we created two buttons, one sets theme--dark on the <body> and the other theme--light. We can now write our styling using these selectors and re-assign different values to our CSS custom properties.

Source code (simplified):

document.getElementById('light-theme-button')
  .addEventListener('click', setLightTheme);
document.getElementById('dark-theme-button')
  .addEventListener('click', setDarkTheme);

function setLightTheme() {
  document.body.classList.add('theme--light');
  document.body.classList.remove('theme--dark');
}
  
function setDarkTheme() {
  document.body.classList.add('theme--dark');
  document.body.classList.remove('theme--light');
}
body.theme--light { --font-color: #283444; }
body.theme--dark { --font-color: #ffffff; }

body.theme { color: var(--font-color); }

View the full manual controls source.

Using geolocation

This is a fun one. We use the Geolocation API to read the user’s latitude and longitude values. We then combine these values with a new Date() object to calculate the sunrise and sunset for the user’s location. We used SunCalc for this. Based on these values we conditionally add the theme--light or them--dark class to the body. We update these values around every minute, so we can update the user’s location on the fly.

Source code (simplified):

if ('geolocation' in navigator) {
  navigator.geolocation.getCurrentPosition(position => {
    const { latitude, longitude } = position.coords;
    setInterval(() => {
      const currentTime = new Date();
      const { sunrise, sunset } = SunCalc.getTimes(
        currentTime, latitude, longitude);
      updateTheme({ currentTime, sunrise, sunset });
    }, 60 * 1000); // Update every minute.
  });
}
  
function updateTheme({ currentTime, sunrise, sunset }) {
  (currentTime > sunrise && currentTime < sunset)
    ? setLightTheme()
    : setDarkTheme();
}
Geolocation browser support table

Using CSS feature query

This experiment only works on MacOS Mojave, and you must use the Safari Technology Preview browser. The code itself is straightforward, and no JavaScript is required. All you need to do is to swap your theme colours inside the (prefers-color-scheme: dark) media query.

Source code (simplified):

:root {
  --background: #ffffff;
  --content: #283444;
  --content-alt: #515c6a;
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #283444;
    --content: #ffffff;
    --content-alt: #acb8c8;
  }
}

View the full CSS feature query source.

Keep in mind that this technique has, at the time of writing, very little browser support. Hopefully this will change with the next version of Safari and other mayor browsers in the future.

Using ambient light sensor

In our last experiment we used the ambient light sensor that is (sometimes) present on the user’s device. Using ambient light, you could make a theme that smoothly changes depending on the current lighting conditions. Make sure you give your users an option to turn this off. Also keep in mind that this feature can be very distracting if a user is in an area where the lighting conditions often change, e.g. streets at night.

Source code (simplified):

if ('AmbientLightSensor' in window) {
  const sensor = new AmbientLightSensor();
  sensor.onreading = () => {
    const illuminance = sensor.illuminance;
    if (illuminance < 50) {
      setDarkTheme();
    } else if (illuminance > 60) {
      setLightTheme();
    }
  };
  sensor.start();
}

View the full ambient light sensor source.

As you can see below, this feature is not well supported yet. The AmbientLightSensor API is only available in some WebKit based browsers. You also need to turn on the "Generic Sensor Extra Classes" feature flag by typing "chrome://flags/#enable-generic-sensor-extra-classes" into the address bar and setting it to 'enabled'. Users will be asked for permission to use the sensors. Safari is currently working on an implementation.

Ambient Light support table
Sensor permission pop-up

For a more in-depth guide, consider reading 'Using the Ambient Light Sensor API to add brightness-sensitive dark mode to my website' by Arnelle Balane.

Putting Everything Together

After experimenting with the mentioned techniques separately, we thought it would be interesting to put them all together into one single demo. In this video we show the 4 techniques combined in a menu. We give the user the option to select what theme they want. They can also select ‘auto’, in which case a theme is chosen based on OS settings, ambient light or their location.

Takeaways

  • There is a lot more involved with Dark Modes than simply inverting the colours. The process for choosing the new colours takes time, is complex and should be done by a designer. It does not only need to look right, but it also needs to be accessible.
  • Most of the techniques used in this experiment are extremely new and not well supported. Therefore, each demo will only work on different browsers. In case you need to support every major browser, you will need to combine multiple techniques, make sure you always add Manual Controls as a fallback.
  • While Geolocation is supported in most modern browsers, most users do not like to share their location. As an alternative, you could use the user’s IP address estimate their approximate position.
  • The AmbientLightSensor API does not seem to work on the newer MacBooks. We weren’t able to figure out if this is a MacOS Mojave bug or if the API does not support the True Tone Sensor that’s commonly used in Apple devices.

Conclusion

We should embrace the fact that ‘dark modes’ will show up in more and more places. Users should be able to decide how they want to experience your content. While most of the techniques mentioned above are quite new and not very well supported, some can be used right now. We do suggest you always provide some form of manual control or the ability to completely turn off dark mode. Some users might prefer the lighter theme.

You can find the code for all the experiments in the following repository or you can play with the demos yourself.

← All blog posts

Also in love with the web?

For us, that’s about technology and user experience. Fast, available for all, enjoyable to use. And fun to build. This is how our team bands together, adhering to the same values, to make sure we achieve a solid result for clients both large and small. Does that fit you?

Join our team