DEV Community

Cover image for PSA: Add dark mode to your sites, or at least let the browsers do it for you
Okiki Ojo
Okiki Ojo

Posted on • Originally published at blog.okikio.dev on

PSA: Add dark mode to your sites, or at least let the browsers do it for you

I have a simple message for web developers, start adding the color-scheme property to your webpages.

<!--
  The page supports both dark and light color schemes,
  and the page author prefers dark.
-->
<meta name="color-scheme" content="dark light">

Enter fullscreen mode Exit fullscreen mode

or you can even add it using css

/*
  The page supports both dark and light color schemes,
  and the page author prefers dark.
*/
:root {
  color-scheme: dark light;
}

Enter fullscreen mode Exit fullscreen mode

I absolutely detest sites that "have a dark mode, BUT DON'T MAKE THE SCROLLBAR DARK!", a great example of this is docusaurus.

docusaurus-scrollbar

Docusarus Why???

burning-eyes

I actually tweeted at them asking why the scrollbar isn't dark as well, 🤣

The light mode scrollbar hurts the eyes, and ruins the look of the site, so, for the sake of everyone who has eyes and likes dark mode, please use color-scheme, you can even use it together with your dark mode toggle by using css, for example, one of the sites I made for a client josephojo.com

When using the color-scheme property you can turn form elements, webpage background, text color, and scrollbars dark, a more famous example would be, github,

github-scrollbar

Notice, how the scrollbar is dark, and doesn't burn the eyes, they're able to do it, by using the meta tag.

github dark mode

For josephojo.com I used the color-scheme css property together with @media (prefers-color-scheme: dark) {} and the .dark class, the final result is

html {
    color-scheme: light;
}

html.dark {
    color-scheme: dark;
}

@media (prefers-color-scheme: dark) {
    html:not([data-theme]) {
        color-scheme: dark;
    }
}

Enter fullscreen mode Exit fullscreen mode

When creating the site I used tailwindcss, with the dark mode set to "class", my tailwind config looked like this,

module.exports = {
    darkMode: 'class',
    // ...
}

Enter fullscreen mode Exit fullscreen mode

For those who haven't used tailwindcss before, it's basically the same as defining a class that when added to the html element will signal that the site is in dark mode.

Or in simpler terms it's,

<html class="dark"> 
    <!-- ... -->
</html>

Enter fullscreen mode Exit fullscreen mode

You: Wait, but, how did you handle the theme toggle button?

Me: I'm glad you asked.

Now that we have some boilerplate code, all you really need to do, is setup a toggle that will remember our current theme state.

While developing josephojo.com, I found that you have to set your theming system to support the native media theme before anything else, it's generally less painful to the user, that's why I set html:not([data-theme]) in the prefers-color-scheme: dark media query,

/* ... */
@media (prefers-color-scheme: dark) {
    html:not([data-theme]) {
        color-scheme: dark;
    }
}
/* ... */

Enter fullscreen mode Exit fullscreen mode

html.dark represents the dark theme applied by tailwind and [data-theme] represents the currently applied theme, if data-theme is different from the local storage, then the theme was manually toggled and the page should use the new theme in data-theme as well as update the local storage theme, otherwise, it should use the local storage theme as data-theme, but because data-theme is only applied to the html element after javascript is loaded we can tell our css to use the default dark theme if prefers-color-scheme: dark and the html element doesn't have the data-theme attribute.

The result you get is this,

As you saw at the end there, changing the actual browser theme won't permanently change the theme set in local storage, with the idea being, if a user a manually changes the theme they must want to use that theme permanently, otherwise use the system theme.

Here is the code for the theme toggle,

// Based on [joshwcomeau.com/gatsby/dark-mode/]
let getSavedTheme = () => {
  const theme = window.localStorage.getItem("theme");
  // If the user has explicitly chosen light or dark,
  // let's use it. Otherwise, this value will be null.
  if (typeof theme === "string") return theme;

  // If they are using a browser/OS that doesn't support
  // color themes, let's not do anything.
  return null;
};

let saveTheme = (theme) => {
  // If the user has explicitly chosen light or dark, store the default theme
  if (typeof theme === "string")
    window.localStorage.setItem("theme", theme);
};

let mediaTheme = () => {
  // If they haven't been explicitly set, let's check the media query
  const mql = matchMedia("(prefers-color-scheme: dark)");
  const hasMediaQueryPreference = typeof mql.matches === "boolean";
  if (hasMediaQueryPreference) return mql.matches ? "dark" : "light";
};

const html = document.querySelector("html");

// Get theme from html tag, if it has a theme or get it from localStorage
let checkCurrentTheme = () => {
  let themeAttr = html.getAttribute("data-theme");
  if (themeAttr) return themeAttr;

  return getSavedTheme();
};

// Set theme in localStorage, as well as in the html tag
let applyTheme = (theme) => {
  html.className = theme;
  html.setAttribute("data-theme", theme);
};

try {
  // if there is a saved theme in local storage use that,
  // otherwise use `prefer-color-scheme` to set the theme 
  let theme = getSavedTheme();
  if (theme == null) theme = mediaTheme();

  // set the initial theme
  html.setAttribute("data-theme", theme);
  html.classList.add(theme);

  // If a user changes the system/browser/OS theme, update the site theme as well,
  // but don't save the change in local storage
  window
    .matchMedia("(prefers-color-scheme: dark)")
    .addEventListener("change", (e) => {
      applyTheme(e.matches ? "dark" : "light");
    });

  // On theme toggle button click, toggle the page theme between dark and light mode,
  // then save the theme in local storage
  document
    .querySelector("#theme-toggle")
    .addEventListener("click", () => {
      let theme = checkCurrentTheme() === "dark" ? "light" : "dark";
      applyTheme(theme);
      saveTheme(theme);
    });
} catch (e) {
  console.warn("Theming isn't available on this browser.", e);
}

Enter fullscreen mode Exit fullscreen mode

You can view the demo below, but you need to open the demo in a new tab for it to work properly ( note , I don't mean open the entire Code Sandbox in a new tab, I specifically mean the demo. CodeSandbox has disabled local storage, when the demo is attached to rest of the CodeSandbox instance, so, you need to detach the demo from the CodeSandbox instance).

Also, notice, how I never set the text color, background color, scrollbar color or button styles, that's part of the magic of setting color-scheme.

You can read more about color-scheme on web.dev

Please tell me what you think about color-scheme in the comments below.

Update: Another cool part feature of the color-scheme meta tag is that Samsung Internet won't force dark mode on your site if it uses the color-scheme meta tag, from what I can tell Chrome might implement a similar feature in the future. I tweeted about it

You can learn more about this on the Samsung Developers site,

https://developer.samsung.com/internet/blog/en-us/2020/12/15/dark-mode-in-samsung-internet


Photo by Alexander Andrews on Unsplash

Top comments (4)

Collapse
 
drumstickz64 profile image
Drumstickz64

Unfortunately this is currently not supported in firefox

Collapse
 
okikio profile image
Okiki Ojo

Unfortunately you are correct, Firefox is the only browser that doesn't support it, but support might be added in the future bugzilla.mozilla.org/show_bug.cgi?....

caniuse.com/?search=color-scheme

Also, From what I can tell, Firefox can automatically change the color of the scrollbar based on the background you set, so, as long as you test your dark mode on Firefox before publishing your site, you should be good.

Collapse
 
manlikecliff profile image
ManLikeCliff

I'd rather handcode it than leave it to the browsers. It's weird how fellas add a dark mode to their site and ignore the scroll bar. Nice article man 🍻

Collapse
 
okikio profile image
Okiki Ojo

Thanks