2

I wanna detect the user prefers-color-scheme and set data-mode attribute to the document root (html) but I can't.

my app.vue code is this:

<template>
<div>
<NuxtLayout />
<NuxtLoadingIndicator color="orange" />
<NuxtPage />
</div>
</template>

<script setup lang="ts">
    if (
        !("theme" in localStorage) &&
        window.matchMedia("(prefers-color-scheme: dark)").matches ) {
            document.documentElement.setAttribute("data-mode", "dark");
        }
</script>

but this page uses SSR because of this I get localStorage or window is not defined

I can't do this in onBeforeMount or onMounted lifecycles because colors change after dom loaded

important notes are these:

  • this page should render on the server side
  • I should detect prefers-color-scheme value before the dom loaded

I know the server has no access to the window, but can I send something to the server based on the system's mode and set the data-mode attribute before getting the HTML document?

sadeq shahmoradi
  • 1,395
  • 1
  • 6
  • 22

2 Answers2

2

As written here, you cannot have several things as root nodes (so wrap them into a div).

Then, window is undefined as of here, meaning that you will need a need to double check that your code is running on the server with a process.server.

<script>
export default {
  created() {
    if (process.server) {
      // run your thing on the server
    }
  },
}
</script>

Finally, you should probably use this Nuxt module: https://github.com/nuxt-modules/color-mode for your purpose, pretty much its whole purpose.

A similar question was also asked here, maybe it can be helpful: Dark mode switcher in Nuxt 3 not working with official @nuxtjs/color-mode

kissu
  • 40,416
  • 14
  • 65
  • 133
  • hello, @kissu yeah, I saw that question before but even in nuxtColorMode, we can't set `mode` based on the user system on the server side because you don't have access to the user system. in this question https://stackoverflow.com/questions/67751476/how-to-fix-navigator-window-document-is-undefined-in-nuxt/67751550#67751550 I think the code runs on the client side, not on the server. that is why we don't get undefined but can I send something from the user system to the server when he wants to visit the site and then set the `mode` on the server side – sadeq shahmoradi Dec 10 '22 at 02:55
  • maybe there was something to check and set `mode` based on the user system's mode in the first request when the user wants to visit the site – sadeq shahmoradi Dec 10 '22 at 02:59
  • @sadeqshahmoradi at the end, this thing cannot be done on the server-side because it's a browser preference. Either you have that setting in a database or you do that when you initially render your page. Pretty much what is [said here](https://v2.color-mode.nuxtjs.org/#caveats). – kissu Dec 10 '22 at 03:00
1

The solution is in app.vue use the useHead function. In app.vue you should write this

<script setup>
useHead({
  script: {
    children: `
      (() => {
        const localStorageTheme = localStorage.getItem('theme') || ''
        const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
        (!localStorageTheme || localStorageTheme === 'auto' ? prefersDark : localStorageTheme === 'dark') && document.documentElement.classList.add('dark')
      })();
    `
  }
})
</script>

What this will do is block the rendering of the page until the script has finished executing since this script tag is not deferred. Usually you don't want to do this but for this critical rendering case it's fine and it's a super small script so you won't run into any problems.

If you want to toggle the theme when a button is clicked you can use a function like this

function toggleTheme() {
  const active_theme = localStorage.getItem('theme')

  if (!active_theme) {
    localStorage.setItem('theme', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'light' : 'dark')
    if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
      document.documentElement.classList.remove('dark')
    }
    else {
      document.documentElement.classList.add('dark')
    }
  }

  if (active_theme === 'light') {
    localStorage.setItem('theme', 'dark')
    document.documentElement.classList.add('dark')
  }

  else if (active_theme === 'dark') {
    localStorage.setItem('theme', 'light')
    document.documentElement.classList.remove('dark')
  }
}
dbzx10299
  • 722
  • 2
  • 14