Realtime color mode change if system color mode changed and color choice not being made

I’ve got a toggle for a color theme switcher for my website. Here’s the JavaScript code for the theme switcher:

$('.theme-switch').on('click',
    function(e) {
        const valChoice = $('html').attr('data-theme') === 'dark' ? 'light' : 'dark';

        window.localStorage.setItem('themeChoice', valChoice);
        $('html').attr('data-theme', valChoice);
    }
);

This is working perfectly. I’ve got other scripts as well to remember a user’s choice when a page loads after the theme choice. It also covers device color settings when a page loads.

I want to cover more than that. I’d like to see changes in real-time if a user changed device color settings when on a page. So I added the script:

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change',
    function(e) {
        const colorScheme = e.matches ? 'dark' : 'light';

        if (colorScheme === 'dark') {
            $('html').attr('data-theme', 'dark');
        } else {
            $('html').attr('data-theme', 'light');
        }
    }
);

The code is working as expected. It responds if there is a change in device color settings. But there is a little problem!

Website color mode should not be changed to device mode if a user has chosen a theme on the running page by clicking the theme toggle. I mean, website mode should not be changed to device mode in real-time if a user has made a theme choice already in the current window.

Can anyone help me do that?

Okay, I need charts to understand this.

Currently when you click the switch and change the preferred scheme, the middle column of behaviour occurs where the page theme changes from default to dark then to light.

prefers \ switch | unused | click | 2nd click
-----------------+--------+-------+----------
default          | light  | dark  | light
dark             | dark   | dark  | dark
light            | light  | light | light

You seem to instead be wanting the following behaviour, where changing the preferred scheme (from default to dark to light) only changes the theme when the switch hasn’t been touched.

prefers \ switch | unused | click | 2nd click
-----------------+--------+-------+----------
default          | light  | dark  | light
dark             | dark   | dark  | light
light            | light  | dark  | light

Is that an accurate representation of the things? That the theme-switch toggle results in completely ignoring the prefers-color-scheme setting?

That should just involve doing an early return in the matchmedia function. If local storage has a value then we’re outta here.

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change',
    function(e) {
      if (window.localStorage.getItem('themeChoice') > '') {
        return;
      }
      ...
2 Likes

Bravo! This conditional did the trick!

Just one question. Why did you use window.localStorage.getItem('themeChoice') > '' instead of just window.localStorage.getItem('themeChoice') ?

Thanks!

I used the condition for clarity, as using just a string value as a condition can and does result in unexpected behaviour. For example, a string of “0” is considered to be false.

The following conditions are considered to be true:

if ("0" == false) {
  // yes it is, but why?
}
if ("0") {
  // This has a value, but is also considered to be false. Weird.
}

There are other weird cases too. To help avoid those when we’re not dealing with true/false values, it’s best to use an actual conditional to avoid weird edge cases, which removes a lot of those potential problems from occurring.

3 Likes