Color mode

Light/dark/auto color mode with an SSR-safe, flash-free theme.

adminlte-vue supports light, dark, and auto color modes built on Bootstrap's data-bs-theme attribute. The preference is persisted to localStorage and applied before first paint in Nuxt, so there is no flash of the wrong theme on reload.

Overview

Color mode is provided by LteDashboardLayout, which calls provideColorMode() in its setup(). Descendant components read and update the mode through the useColorMode() composable. The state is:

  • colorMode — the user's preference: 'light' | 'dark' | 'auto'.
  • resolvedMode — the concrete mode applied to the page: 'light' | 'dark'. When colorMode is 'auto', it follows the system prefers-color-scheme setting.

The resolved mode is written to data-bs-theme on the <html> element, and the preference is saved under the lte-theme localStorage key.

Usage

The toggle component

LteColorModeToggle is a ready-made dropdown (rendered as a navbar <li>) that lets the user pick Light, Dark, or Auto. It takes no props and must live inside LteDashboardLayout — typically in the topbar's navbar list. In Nuxt it is auto-registered, so no import is needed.

<template>
  <ul class="navbar-nav ms-auto">
    <ClientOnly>
      <LteColorModeToggle />
    </ClientOnly>
  </ul>
</template>

Wrapping the toggle in <ClientOnly> avoids a hydration mismatch on the trigger glyph, which depends on the persisted preference resolved on the client.

Reading and setting the mode

Use useColorMode() anywhere inside the layout to read or change the mode programmatically.

<script setup lang="ts">
const { colorMode, resolvedMode, setColorMode } = useColorMode()
</script>

<template>
  <div>
    <p>Preference: {{ colorMode }} (resolved: {{ resolvedMode }})</p>
    <button @click="setColorMode('light')">Light</button>
    <button @click="setColorMode('dark')">Dark</button>
    <button @click="setColorMode('auto')">Auto</button>
  </div>
</template>

colorMode is a writable Ref, so colorMode.value = 'dark' works too — setColorMode() is just a convenience wrapper.

useColorMode() API

useColorMode() injects the color-mode state provided by LteDashboardLayout. Calling it outside the layout throws an error.

NameTypeDescription
colorModeRef<'light' | 'dark' | 'auto'>The user's color-mode preference. Writable.
resolvedModeComputedRef<'light' | 'dark'>The concrete mode applied to the page; resolves 'auto' against the system setting.
setColorMode(mode: 'light' | 'dark' | 'auto') => voidSet the preference. Persists to localStorage and updates data-bs-theme.
import { useColorMode } from 'adminlte-vue'

const { colorMode, resolvedMode, setColorMode } = useColorMode()

Setting the initial mode

The initial preference defaults to 'auto'. In Nuxt, set it through the module's defaults:

export default defineNuxtConfig({
  modules: ['@adminlte/nuxt'],
  adminlte: {
    defaults: {
      initialColorMode: 'auto', // 'light' | 'dark' | 'auto'
    },
  },
})

This value is used as the fallback by the blocking head script and seeds the layout's initial state. A persisted lte-theme value always takes precedence once the page loads.

How the flash is avoided in Nuxt

A flash of the wrong theme happens when the saved preference is only applied after Vue hydrates. To prevent it, @adminlte/nuxt injects a small blocking inline <head> script (keyed lte-theme-init) that runs before first paint. It reads lte-theme from localStorage (falling back to initialColorMode), resolves auto against prefers-color-scheme, and sets data-bs-theme on <html> immediately:

(function () {
  try {
    var k = localStorage.getItem('lte-theme') || 'auto'
    var d = k === 'auto'
      ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
      : k
    document.documentElement.setAttribute('data-bs-theme', d)
  } catch (e) {}
})()

After hydration, useColorMode() owns all reactive updates — it reconciles with the persisted preference, listens for system prefers-color-scheme changes (while in auto), and re-applies data-bs-theme whenever the mode changes.

The script is on by default. You can disable it with the themeScript module option (for example if you inject your own):

export default defineNuxtConfig({
  modules: ['@adminlte/nuxt'],
  adminlte: {
    themeScript: false,
  },
})