Theming & Dark Mode

AdminLTE 4 uses Bootstrap 5.3's native color modes: the theme is the data-bs-theme attribute on <html>. A light / dark / auto toggle persists the choice to localStorage, and a small no-flash script applies it before first paint. Deeper theming is done with CSS custom properties or SCSS variables.

How dark mode works

Bootstrap 5.3 recolours every component based on data-bs-theme="light" or data-bs-theme="dark" on the root element. AdminLTE simply manages that attribute. There are three pieces:

  • A no-flash init script in <head> that sets the attribute synchronously before the page renders.
  • A toggle in the topbar with Light / Dark / Auto options.
  • A controller script at the end of <body> that wires the toggle and persists the choice.

The chosen theme is stored under the localStorage key lte-theme (values light, dark, or auto).

No-flash init (head)

Place this inline script as the first thing in <head>. Running synchronously, it prevents a flash of the wrong theme on load. Explicit dark/light win; otherwise it falls back to the OS preference:

<script>
  (() => {
    'use strict';
    const STORAGE_KEY = 'lte-theme';
    let stored = null;
    try { stored = localStorage.getItem(STORAGE_KEY); } catch {}
    const prefersDark = globalThis.matchMedia('(prefers-color-scheme: dark)').matches;
    let resolved = 'light';
    if (stored === 'dark' || stored === 'light') {
      resolved = stored;
    } else if (prefersDark) {
      resolved = 'dark';
    }
    document.documentElement.setAttribute('data-bs-theme', resolved);
    document.documentElement.style.colorScheme = resolved;
  })();
</script>

The toggle

A Bootstrap dropdown whose options carry data-bs-theme-value. The controller reads that value, applies it, and stores it:

<li class="nav-item dropdown">
  <button class="btn btn-link nav-link dropdown-toggle" id="bd-theme"
          type="button" data-bs-toggle="dropdown" aria-expanded="false">
    <span class="theme-icon-active"><i class="my-1"></i></span>
  </button>
  <ul class="dropdown-menu dropdown-menu-end">
    <li><button type="button" class="dropdown-item" data-bs-theme-value="light">
      <i class="bi bi-sun-fill me-2"></i>Light
      <i class="bi bi-check-lg ms-auto d-none"></i></button></li>
    <li><button type="button" class="dropdown-item" data-bs-theme-value="dark">
      <i class="bi bi-moon-fill me-2"></i>Dark
      <i class="bi bi-check-lg ms-auto d-none"></i></button></li>
    <li><button type="button" class="dropdown-item" data-bs-theme-value="auto">
      <i class="bi bi-circle-half me-2"></i>Auto
      <i class="bi bi-check-lg ms-auto d-none"></i></button></li>
  </ul>
</li>

The controller (end of body)

The controller applies the preferred theme, resolves auto against the OS setting, persists clicks to lte-theme, and re-resolves auto when the OS preference changes:

(() => {
  'use strict';
  const STORAGE_KEY = 'lte-theme';
  const prefersDark = () => matchMedia('(prefers-color-scheme: dark)').matches;
  const getPreferredTheme = () => localStorage.getItem(STORAGE_KEY)
    || (prefersDark() ? 'dark' : 'light');
  const setTheme = (theme) => {
    const resolved = theme === 'auto' ? (prefersDark() ? 'dark' : 'light') : theme;
    document.documentElement.setAttribute('data-bs-theme', resolved);
  };
  setTheme(getPreferredTheme());

  matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
    const stored = localStorage.getItem(STORAGE_KEY);
    if (!stored || stored === 'auto') setTheme(getPreferredTheme());
  });

  document.addEventListener('DOMContentLoaded', () => {
    document.querySelectorAll('[data-bs-theme-value]').forEach((toggle) => {
      toggle.addEventListener('click', () => {
        const theme = toggle.getAttribute('data-bs-theme-value');
        localStorage.setItem(STORAGE_KEY, theme);
        setTheme(theme);
      });
    });
  });
})();

The demo applies data-bs-theme="dark" directly on .app-sidebar to get the dark-sidebar-on-light-page look — data-bs-theme can be scoped to any element, not just <html>. Drop it for a sidebar that follows the page theme.

SCSS / CSS theming

For quick retheming with no build step, override Bootstrap's CSS custom properties in your own stylesheet:

:root, [data-bs-theme="light"] {
  --bs-primary: #6610f2;
  --bs-primary-rgb: 102, 16, 242;
  --bs-body-bg: #f3f4f6;
}
[data-bs-theme="dark"] {
  --bs-body-bg: #14171c;
  --bs-body-color: #e9ecef;
}

Most of AdminLTE's chrome is built on var(--bs-primary) and friends, so it picks these up automatically. For structural changes (sidebar width, breakpoints, spacing scale) that CSS variables don't cover, override SCSS variables and recompile — see Configuration.


AdminLTE 4 · HTML port Edit on GitHub