29th jul 2024

·

2 min read

how to add themes in Nuxt3 without flashing

using nuxt-class-inject for dynamic styling.

#nuxt3 #nuxt-class-inject #tailwnd-css

All the source code is available on GitHub for both the pure CSS example

and the Tailwind CSS example
.

demo

First install nuxt-class-inject using a package manager

bash

npm install --save-dev nuxt-class-inject

Add it to the project‘s modules in nuxt.config.ts:

typescript

export default defineNuxtConfig({
  modules: [
    "nuxt-class-inject", 
  ],
});

Define some color themes as CSS classes. I‘m going to define them directly in vue.app but you can also define these in a global CSS file—just make sure to register it in Nuxt config.

vue

<style lang="css">
.theme-light {
    background-color: #1e1e1e;
    color: #fefefe;
}

.theme-dark {
    background-color: #fefefe;
    color: #1e1e1e;
}
</style>

Add some logic to allow for theme switching using the nuxt-class-inject API through the provided $classInject. Note, $classInject exposes a list of all the injected classes so we need to fully overwrite it for vue‘s reactivity system to update our DOM.

vue

<script setup lang="ts">
const { $classInject } = useNuxtApp();

const themes: string[] = ["theme-light", "theme-dark"];
const setTheme = (theme: string) => {
    const current: string[] = $classInject.classList.value;

    const classList = current.filter((cls) => {
      return !cls.startsWith("theme-")
    });
    classList.push(theme);

    $classInject.classList.value = classList; // overwrite
};
</script>

Add some buttons to switch between themes.

vue

<template>
    <div>
        <button
            v-for="theme in themes"
            @click="setTheme(theme)"
        >
            {{ theme }}
        </button>
    </div>
</template>

That‘s it.

It is also possible to use this module with Tailwind CSS if that‘s more your style, all you have to do is set variables in your theme classes instead of properties

vue

<style lang="css">
.theme-light {
    --foreground: #1e1e1e;
    --background: #fefefe;
}

.theme-dark {
    --foreground: #fefefe;
    --background: #1e1e1e;
}
</style>

Register them as custom Tailwind classes in tailwind.config.js

javascript

/** @type {import('tailwindcss').Config} */
export default {
    theme: {
        extend: {
            colors: {
                foreground: "var(--foreground)",
                background: "var(--background)",
            },
        },
    },
};

And use them as properties

vue

<template>
    <div class="w-full min-h-screen bg-background text-foreground">
        <button
            v-for="theme in themes"
            @click="setTheme(theme)"
        >
            {{ theme }}
        </button>
    </div>
</template>

The CSS classes can set any variables you‘d like, so you can also use this module to customize everything with a CSS property. Just include multiple classes in the injected class list

typescript

const fontTypes: string[] = ["serif", "sans", "mono"];
const setFontType = (font: string) => {
    const current: string[] = $classInject.classList.value;

    const classList = current.filter((cls) => 
        !cls.startsWith("font-")
    ).push(`font-${font}`);

    $classInject.classList.value = classList;
};

const themes: string[] = ["light", "dark"];
const setTheme = (theme: string) => {
    const current: string[] = $classInject.classList.value;

    const classList = current.filter((cls) => 
        !cls.startsWith("theme-")
    ).push(`theme-${theme}`);

    $classInject.classList.value = classList;
};