N
Nuxt3mo ago
merd

What is the best way to change HTML attributes before the page renders?

I am trying to create a dark mode / light mode toggle for my website using localStorage. I set up a plugin and used useHead() function to set the attributes but that caused light mode to be flashed. I asked Claude what should I do and it sent me this:
export default defineNuxtPlugin((nuxtApp) => {

useHead({
script: [
{
innerHTML: `
(function() {
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
})();
`,
type: 'text/javascript'
}
]
})
})
export default defineNuxtPlugin((nuxtApp) => {

useHead({
script: [
{
innerHTML: `
(function() {
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
})();
`,
type: 'text/javascript'
}
]
})
})
Works with no problems, but seems a bit hack-y. Is there a better way to go about this? Or managing dark / light mode... Thanks!
2 Replies
kapa.ai
kapa.ai3mo ago
Hi @merd. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- The best practice in Nuxt for managing dark/light mode and setting HTML attributes—without causing a flash of incorrect theme (commonly called a "flash of unstyled content" or FOUC)—is to use the official color mode integration rather than a manual localStorage script. Recommended Approach: Nuxt UI and the @nuxtjs/color-mode module provide a robust, SSR-friendly solution for theme toggling. This integration handles theme persistence, SSR hydration, and updates HTML attributes for you, avoiding the FOUC issue. You can use the useColorMode composable to manage the theme. For example:
<script setup>
const colorMode = useColorMode()

const isDark = computed({
get() {
return colorMode.value === 'dark'
},
set(_isDark) {
colorMode.preference = _isDark ? 'dark' : 'light'
}
})
</script>

<template>
<ClientOnly v-if="!colorMode?.forced">
<UButton
:icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"
color="neutral"
variant="ghost"
@click="isDark = !isDark"
/>
<template #fallback>
<div class="size-8" />
</template>
</ClientOnly>
</template>
<script setup>
const colorMode = useColorMode()

const isDark = computed({
get() {
return colorMode.value === 'dark'
},
set(_isDark) {
colorMode.preference = _isDark ? 'dark' : 'light'
}
})
</script>

<template>
<ClientOnly v-if="!colorMode?.forced">
<UButton
:icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"
color="neutral"
variant="ghost"
@click="isDark = !isDark"
/>
<template #fallback>
<div class="size-8" />
</template>
</ClientOnly>
</template>
This approach ensures the correct theme is applied as early as possible, both on the server and client, and avoids the flash of the wrong theme. You do not need to manually manipulate localStorage or inject scripts into the head—Nuxt's color mode module handles this for you, including setting the appropriate HTML attributes and persisting the user's preference across sessions and SSR/CSR boundaries. Why not use localStorage directly? Directly accessing localStorage in SSR (server-side rendering) will not work, and even on the client, it can cause hydration mismatches or flashes of the wrong theme. Nuxt's color mode module solves these issues by handling theme persistence in a universal (SSR/CSR) way and updating the DOM as early as possible in the render process, which is not possible with a plugin that runs after hydration has started. Summary:
Use the official color mode integration (@nuxtjs/color-mode with useColorMode) for best results. Avoid manual localStorage scripts for theme toggling in Nuxt, as they are not SSR-friendly and can cause visual glitches.
Color Mode in Nuxt UI If you need to set other HTML attributes dynamically (like lang or dir), use the useHead composable, which is SSR-friendly and reactive, but for theme toggling, the color mode module is the best solution.
useHead composable ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@153852225695907841> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?