Color mode

Light/dark/auto color mode with an SSR-safe, flash-free theme, plus the opt-in color palettes.

@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,
  },
})

Extended color palettes

Color mode is about light vs. dark. If you also want more colors, AdminLTE core ships two opt-in stylesheets, re-exported by this package. Neither is loaded by default — @adminlte/vue/css is unchanged — and you load one or the other, never both, because they share class names.

ExportContents
@adminlte/vue/css/colorsThe designed palette (core 4.4.0) — 14 extra colors (orange, amber, olive, teal, sky, indigo, violet, fuchsia, pink, navy, steel, slate, graphite, midnight), each generated so white text on it clears WCAG AA.
@adminlte/vue/css/colors/v3The AdminLTE 3 palette (core 4.5.0) — the original 18 colors with their v3 values and v3's text-color rule.

Both add the same class families for their colors: .bg-*, .text-bg-*, .text-*, .border-*, .link-*, .bg-gradient-*, .card-*, .callout-* and .direct-chat-*.

export default defineNuxtConfig({
  modules: ['@adminlte/nuxt'],
  css: ['@adminlte/vue/css/colors'], // or '@adminlte/vue/css/colors/v3'
})
import '@adminlte/vue/css'
import '@adminlte/vue/css/colors'

Making a palette color the primary

With one of the sheets loaded, data-lte-primary on <html> (or on any container) repoints Bootstrap's primary at that color — buttons, links, pagination, .nav-pills, progress bars, list groups, accordions and form-control / checkbox focus rings all follow, in both color modes:

export default defineNuxtConfig({
  app: { head: { htmlAttrs: { 'data-lte-primary': 'teal' } } },
})

That means the components you already render with theme="primary"LteCard, LteSmallBox, LteButton, LteCallout, LteDirectChat, … — become teal without touching your markup. The typed theme props stay the eight Bootstrap names; the palette colors are used as plain classes (class="text-bg-teal", class="callout callout-olive") wherever you write markup yourself.

AA text on the v3 palette

Eight of the AdminLTE 3 colors fall short of 4.5:1 with the white text v3 gave them. Adding data-lte-contrast="aa" alongside the v3 sheet flips exactly those eight to the ink that passes, leaving every background unchanged. It is a no-op for the designed palette, which is AA by construction.