nextra icon indicating copy to clipboard operation
nextra copied to clipboard

Automatic dark mode based on system preferences

Open chilikasha opened this issue 2 years ago • 7 comments

macOS 12.3

"next": "^12.1.6",
"nextra": "^1.1.0",
"nextra-theme-docs": "^1.2.6",

Hi, If dark theme is enabled in macOS system preferences, then you still have to manually enable dark mode on the website. Is it a known issue/limitation? Would be nice if it could follow system preferences.

Another case:

  1. light theme is ON in system preferences
  2. open nextra based website
  3. click dark mode button

Actual result: all styling which is added to public/style.css in @media (prefers-color-scheme: dark) {} block is ignored.

chilikasha avatar Jun 07 '22 20:06 chilikasha

Hello @chilikasha ,

You can validate if the user prefers dark-mode at page render.

This is an example code:

// _app.js
import { useEffect } from "react"
import "nextra-theme-docs/style.css"

const PREFERS_DARK_MODE = "(prefers-color-scheme: dark)"
export default function Nextra({ Component, pageProps }) {
  useEffect(() => {
    // https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener
    function handleColorScheme(e) {
      const prefersDarkMode = e && e.matches
      const prefersLightMode = !prefersDarkMode
      const darkActive = document.querySelector(".dark")
      const lightActive = !darkActive
      const toggleButton = document.querySelector("nav [aria-label*='theme']")
      if (
        (prefersDarkMode && lightActive) ||
        (prefersLightMode && darkActive)
      ) {
        if (toggleButton) {
          // If button exists we trigger click event
          toggleButton.click()
        } else {
          // If content isn't yet available we store user preferences in localStorage
          localStorage.setItem("theme", prefersDarkMode ? "dark" : "light")
        }
      }
    }
    const matchedMedia = window.matchMedia(PREFERS_DARK_MODE)
    matchedMedia.addEventListener("change", handleColorScheme) // We listen to match-media changes
    handleColorScheme(matchedMedia) // On-first-render match validation
    return () => {
      matchedMedia.removeEventListener("change", handleColorScheme)
    }
  }, [])
  return <Component {...pageProps} />
}

This will change to dark-mode if the user expects a dark color scheme.

To consider: Theme will keep changing to dark-mode on reload if dark color scheme is expected, this happens even if the user toggles to light-mode from Nextra UI.

Also left some comments and doc references. Hope this helps.

D3Portillo avatar Jun 13 '22 12:06 D3Portillo

hi @D3Portillo, thanks for your help! I tried the snippet above and have the following behaviour: set mac theme to dark, open private tab and open my site -> it opens with light theme, but if I refresh the site then dark theme is applied.

chilikasha avatar Jun 17 '22 21:06 chilikasha

I'm glad you tested it @chilikasha .

Here is a solution for the issue you mentioned.

Inside first if condition:

if (toggleButton) {
  // If button exists we trigger click event
  toggleButton.click()
} else {
  const newTheme = prefersDarkMode ? "dark" : "light"
  // If content isn't yet available we store user preferences in localStorage
  localStorage.setItem("theme", newTheme)
  const html = document.querySelector(".dark, .light")
  // To render content in dark/light mode we append the desired them in html node
  html.classList.remove("dark") // remove .dark if present
  html.classList.remove("light") // remove .light if present
  html.classList.add(newTheme) // append dark or light as per user prefs.
  // Inn case of fire and script above won't work - uncomment line bellow
  // setTimeout(()=> location.reload())
}

NOTE: Just in case that won't work - try triggering a reload(uncomment line with setTimeout) to apply new theme.

D3Portillo avatar Jun 17 '22 21:06 D3Portillo

This feature is available in the new design, but you have to use an alpha version of both nextra and nextra-theme-docs.

jacobhq avatar Jun 25 '22 17:06 jacobhq

Updated to beta versions today, but don't see any changes regarding dark mode. Works the same way.

chilikasha avatar Jul 14 '22 15:07 chilikasha

My bad, that was alpha. works fine on beta

chilikasha avatar Jul 22 '22 10:07 chilikasha

Though I still don't understand why if select a scheme different from system's, colors for some custom elements defined in css are not switched.

@media (prefers-color-scheme: dark) {
  .tag {
    background-color: #2b2b2b;
    color: #bbb9bb;
  }
}

SCR-20220725-u3b SCR-20220725-u30

chilikasha avatar Jul 25 '22 19:07 chilikasha

You can use this:

.dark .tag {
  background-color: #2b2b2b;
  color: #bbb9bb;
}

The .dark class will be added to <html> when one of these 2 conditions is met:

  1. User set the theme to dark
  2. User set the theme to system and the system is under the dark mode

shuding avatar Jan 21 '23 19:01 shuding