shreshthmohan / next-blog

next-blog-mu-three.vercel.app
2 stars 0 forks source link

Dark mode that just works #50

Open shreshthmohan opened 2 years ago

shreshthmohan commented 2 years ago

url: https://shreshth.dev/blog/dark-mode-that-just-works slug: dark-mode

We, as developers who blog, don't need JavaScript to support dark mode!

In fact, if you use JS to make dark mode work, it most likely won't work for users who have it disabled.

I will attempt to convince you that you don't need any JS and minimal CSS to get dark mode to work for your blog site. And I shall show you how to do it.

Use color-scheme meta tag or CSS property

color-scheme will get your site to look decent by making use of the browser's defaults for dark mode.

You can use either the meta tag[^1] or the CSS property[^2], but prefer the meta tag. Reason stated below.

Meta tag

  <!-- add this inside the <head> tag  -->
  <!-- the order in which you write defines the preferred color scheme if none specified by the OS setting -->
  <meta name="color-scheme" content="dark light" />

One would prefer the use of this meta tag instead of CSS to prevent a flash of unstyled content (FOUC):

Honoring the color-scheme CSS property requires the CSS to be first downloaded (if it is referenced via <link rel="stylesheet">) and to be parsed. To aid user agents in rendering the page background with the desired color scheme immediately, a color-scheme value can also be provided in a [meta tag.][^3]

Demo for using the color-scheme meta tag

CSS property

:root {
  color-scheme: dark light;
  /* the order in which you write defines the preferred color scheme if none specified by the OS setting */
}

Demo for using the color-scheme CSS property

Use the prefers-color-scheme media query to override user-agent styles

Now if you don't like the browser's default styles for dark mode, you can override them using the prefers-color-scheme media query[^4].

<meta name="color-scheme" content="dark light" />
<style>
@media (prefers-color-scheme: dark) {
  body {
    background-color: #111; /* Default on Chrome is black (#000) */
    color: #eee; /* Default on Chrome is white (#fff)  */
  }
}
</style>

Having already set the color-scheme you will only need to worry about those elements whose user-agent stylesheet you don't particularly like.

For example, Chrome sets black as the background and white as the default text color, but designers don't recommend using pure black and white. GitHub's dark mode sets #c9d1d9 as the text color and #0d1117 as the background color in dark mode. These are easier on the eyes than browser defaults.

Demo for using color-scheme meta tag with prefers-color-scheme media query

Use some JS if you really want (still an unsatisfactory solution)

Now, some of you might be itching to add some JS.

Well, in my opinion, you don't really need it, but one can argue that the user might find it time-consuming to go into their OS settings to change the appearance setting.

To solve that, you can give them a button to quickly change the dark mode setting directly on your site.

I would do the following.

  1. Respect the user's OS setting using the color-scheme meta tag and prefers-color-scheme media query as mentioned above.
  2. On top of that, allow them to override the above directly on the page. We can use a button to toggle dark mode using some JS.
<head>
  <meta name="color-scheme" content="dark light" />
  <style>
    html {
      color: darkslateblue;
      background-color: oldlace;
    }
    @media (prefers-color-scheme: dark) {
      html {
        color: ivory;
        background-color: darkslategray;
      }
    }
    .dark {
      color: ivory;
      background-color: darkslategray;
    }
    .light {
      color: darkslateblue;
      background-color: oldlace;
    }
  </style>
</head>
<body>
  <button id="toggle-dark">Toggle dark mode</button>
  <!-- some content -->

  <script>
    const toggleDarkModeButton = document.getElementById("toggle-dark");

    toggleDarkModeButton.addEventListener("click", () => {
      // Check if top level element has a dark class
      const manualDarkMode = Array.from(
        document.documentElement.classList
      ).includes("dark");
      // Toggle dark mode
      if (manualDarkMode) {
        document.documentElement.classList.remove("dark");
        document.documentElement.classList.add("light");
      } else {
        document.documentElement.classList.remove("light");
        document.documentElement.classList.add("dark");
      }
    });
  </script>
</body>

demo for manually overriding OS appearance setting with JS But using JS still poses problems with elements you haven't explicitly overridden styles for, say, scrollbars on overflowing content. You may not even be able to solve that entirely because of the lack of CSS properties to style several elements (e.g. scrollbars)

Conclusion

In conclusion, it's best to use the color-scheme meta tag with prefers-color-scheme media query to support dark mode properly. Using JS is mostly going to worsen the user experience.

Demo for using color-scheme meta tag with prefers-color-scheme media query

[^1]: color-scheme meta tag in html spec [^2]: color-scheme CSS property on MDN [^3]: color-scheme meta tag section from the web.dev article on Improved dark mode default styling [^4]: prefers-color-scheme media query on MDN

shreshthmohan commented 2 years ago

Dark mode survey: dev channel on medium

shreshthmohan commented 2 years ago

What I want to achieve:

  1. Support dark mode without JS (with CSS prefers-color-scheme media query)
  2. Allow the user to change the theme for that session. We won't store their preference because the next time they visit the site, it might be at another time.

Seems like it would be challenging to achieve this with Tailwind CSS.

shreshthmohan commented 2 years ago

Disable JS on overreacted.io and swyx.io and change your OS color scheme setting. The theme on these sites won't change.

Fix: Listen to changes to prefers-color-scheme. Wait but this won't work when JS is disabled.