4

Dark Mode Toggles Should be a Browser Feature

 2 years ago
source link: https://www.bram.us/2022/05/25/dark-mode-toggles-should-be-a-browser-feature/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
browser-dark-mode.png

Mockup of a browser in Dark Mode, a button next to the Omnibox that has Light Mode activated (🌕), and the website respecting that

When implementing Dark Mode and ways to toggle it, developers currently need to duplicate code and roll their own toggle implementation.

What if, instead, browsers would take care of all that?

Table of Contents

# Dark Mode 101

Thanks to Media Queries Level 5 we can implement Dark Mode using only CSS. By also using CSS Custom Properties in the mix, it becomes very manageable:

html {
    color-scheme: light dark; /* This site supports both light and dark mode */
}

:root { /* Default Light Mode colors */
    --primary-color: black;
    --primary-background: white;    
}

@media(prefers-color-scheme: dark) {
    :root { /* Dark Mode colors */
        --primary-color: white;
        --primary-background: black;    
    }
}

body {
    color: var(--primary-color);
    background: var(--primary-background);
}

Depending on the System/OS Color Theme setting, the site will either use black text on a white background (Light Mode) or white text on a black background (Dark Mode).

# The problem with Dark Mode Toggles

To offer users an easy way to switch between Light and Dark Mode, a lot of developers are offering toggle buttons their website. Instead of having the user go to the System Preferences, they can switch between Light/Dark Mode without ever leaving the site.

Developers who ever did implement a Light/Dark Mode toggle, might have noticed these side-effects/quirks:

  1. To make this work, developers need to rely on either the Checkbox Hack or JavaScript to alter something – mostly a class or attribute – on the document:

    <select name="color-scheme-">
         <option value="system">System</option>
         <option value="light">Forced Light</option>
         <option value="dark">Forced Dark</option>
     </select>
    
    document.querySelector('color-scheme').addEventListener('change', (e) => {
         document.documentElement.setAttribute('data-force-color-mode', e.target.value);
     });
  2. Because of the way it is implemented (see step 1), duplication of the CSS code is required:

    :root,
     :root[data-force-color-mode="light"] { /* Default Light Mode colors + Forced Light Mode */
         --primary-color: black;
         --primary-background: white;    
     }
    
     /* Dark Color Scheme (System Preference) */
     @media (prefers-color-scheme: dark) {
         :root {
             --primary-color: white;
             --primary-background: black;    
         }
     }
     /* Dark Color Scheme (Override) */
     :root[data-force-color-mode="dark"] {
         --primary-color: white;
         --primary-background: black;    
     }
    
  3. To persist the setting across pages and reloads, JavaScript is required:

    • Write the selected value after changing it (Most likely a cookie or Local Storage)
    • Read the value on page load, to make sure the proper override class is set on the document
  4. You'll need some more JavaScript as well to have the page respond to changes of the System Preference.

    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
     mediaQuery.addListener(() => {
         // Make sure the dropdown is up-to-date based on mediaQuery.matches
     });
    

    But then again: do you still respond if the override has been set?

Above that many implementations I have seen don't take the "System" value into account. By omitting this option, the sites will never be able to respond to the system preference again, as they always have an override applied.

# Code Duplication as a red flag

One the things that immediately stands out as a red flag is the Code Duplication part. It's been bothering me before, and it is still bothering me today. The other listed issues are also issues, but are somewhat acceptable.

Thinking of ways to solve the Code Duplication part, I see these ideas floating around:

  • If we had a :media() pseudo-class available (see this proposal by Lea Verou), they could query it inside :is()

    :root:is(.dark, :media(prefers-color-scheme: dark)) {
          --primary-color: white;
          --primary-background: black;    
      }
    
  • In similar fashion, if Media Queries could also be used as selectors (see this suggestion), you can also group the selectors together

    @media (prefers-color-scheme: dark) p,
    .dark-mode p {
          --primary-color: white;
          --primary-background: black;    
      }
    

    (Sidenote: I’m not a fan of this suggestion to be honest)

  • If we were able to programmatically change the value for prefers-color-scheme, the duplicated CSS part could be omitted. Something like:

    document.querySelector('color-scheme').addEventListener('change', (e) => {
          window.setMedia('prefers-color-scheme', e.target.value);
      });
    

These solutions, however, do not tackle any of the other listed issues. What about the JS requirement to respond to toggle changes, persist the value, read the value on load, …?

# A better way of toggling

What if, instead of having developers try and implement their own toggle and all its quirks, the browser would offer this functionality to the user?

Think of a button that is part of the browser's UI that you could click. Something like the button Chrome DevTools already sports, but then part of the browser UI, available for all users

With such a button in place, the user can easily toggle the preference, and developers don't need to do anything to offer easy switching.

Above that it would allow users to have their OS set to Dark Mode, while reading websites in Light Mode (or vice versa)

# Inherited Settings

The way I see it, the chosen Dark/Light value would be persisted on a per-site basis – i.e. altering it would store the chosen preference for only that site.

To not force users to set the value for every new site they visit, there would also be a setting at the browser level.

Combined, you'd get a value that inherits down from the system level down to the site level:

  1. Dark/Light Setting at the OS level
  2. Dark/Light Setting at the Browser level (default value: inherit from OS)
  3. Dark/Light Setting at the Site level (default value: inherit from Browser)

With these in place, users get the freedom to have the sites they visit use a different color scheme from their OS (if they want) and to also make exceptions for certain websites.

# Demo

You can play around with the CodePen below to see how altering these settings at the various levels would propagate onto a dummy browser UI + two webpages:

Be sure to try out the values to OS:Light+Browser:Dark+Site:Light for bram.us, and then switch tabs to other.site:

  • The site bram.us will be Light, due to the Site setting being Light
  • The site other.site will be Dark, due to the Browser setting being Dark and its specific Site setting being inherit

👏 Shout out to Firefox for shipping a Dark/Light Setting at the Browser level in their Firefox 100 release. They also have an extra option, where the chosen browser theme can decide whether to apply Light or Dark Mode.

firefox-dark-mode.png

Now let's hope we can also get a setting at the Site Level?

# In Summary

In summary, I think there should be easier ways to switch Dark Mode on a per-site basis. Ideally, switching/overriding should be a simple button (or the like) at the browser level. That way we can get rid of all those custom light/dark mode buttons, and the browsers can sync the preference values to all the devices a user uses.

Something that could also make the developers lives easier – but would still leave them custom buttons in place – is a way to programmatically change the value from within JavaScript

window.setMedia('prefers-color-scheme', 'dark');

Or perhaps maybe even both? A scenario where calls to window.setMedia() would bubble up to the browser (so they can keep their UI in sync? it would be an entirely optional thing to implement for devs, as the browser already provides the button.

Let me know what you think in the comments, or hit me up on Twitter.

# Spread the word

To help spread the contents of this post, feel free to retweet the announcement tweet:

When implementing Dark Mode and ways to toggle it, developers currently need to duplicate code and roll their own toggle implementation.

What if, instead, browsers would take care of all that toggle?

🔗 https://t.co/VIazw2a5gB

🏷 #CSS #JavaScript #DarkMode pic.twitter.com/aEKv6mV6Qj

— Bram.us (@bramusblog) May 25, 2022

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK