Theming & Dark Mode
AdminLTE 4 uses Bootstrap 5.3's native color modes: the theme is the data-bs-theme attribute on <html>. Since 4.1.0 the JS bundle ships a ColorMode module that manages light / dark / auto, persists the choice to localStorage, and binds the toggle markup automatically — only a small no-flash script in <head> stays inline. 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 carrying
data-bs-theme-value. - The ColorMode module in the AdminLTE bundle, which wires the toggle, persists the choice, and resolves
autoagainst the OS preference.
The chosen theme is stored under the localStorage key lte-theme (values light, dark, or auto).
No-flash init (head)
This is the one piece that must stay inline: the AdminLTE bundle loads at the end of <body>, too late to stop a flash of the wrong theme. Place this script as the first thing in <head> — running synchronously, it applies the stored theme before first paint. 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 bundled ColorMode module finds these buttons automatically — drop the markup in and it works, no wiring code. Provide one indicator icon per theme with data-lte-theme-icon="light|dark|auto"; the module shows the active one and hides the rest:
<li class="nav-item dropdown">
<button class="btn btn-link nav-link dropdown-toggle" id="bd-theme" aria-label="Toggle color scheme"
type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-sun-fill" data-lte-theme-icon="light"></i>
<i class="bi bi-moon-fill d-none" data-lte-theme-icon="dark"></i>
<i class="bi bi-circle-half d-none" data-lte-theme-icon="auto"></i>
</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 ColorMode module (in the bundle)
Since 4.1.0 the theme controller is part of adminlte.js — there is no copy-paste script to maintain anymore (in earlier 4.0.x releases this was an inline script at the end of <body>; delete it when upgrading). The module:
- applies the preferred theme on load and persists clicks under the
lte-themestorage key, - binds every
[data-bs-theme-value]toggle automatically and marks the active option, - resolves
autoagainst the OS setting and re-resolves it when the OS preference changes, - syncs
[data-lte-theme-icon]indicators with the active theme's icon.
Every change dispatches a changed.lte.color-mode event on document — use it to re-render anything that doesn't pick up CSS variables automatically (charts are the classic case):
document.addEventListener('changed.lte.color-mode', (event) => {
const { theme, resolved } = event.detail; // e.g. theme: 'auto', resolved: 'dark'
myChart.updateOptions({ theme: { mode: resolved } });
});
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.
Server-rendered themes
Since 4.2.0 ColorMode resolves the theme in a defined order, so a theme your backend rendered is not thrown away:
- the visitor's stored choice (
localStorage, keylte-theme), - otherwise a theme the page declared itself in
<html data-bs-theme="light|dark">, - otherwise the OS preference.
So if you render the theme from a cookie or a user record, just emit it and ColorMode will honour it:
<html lang="en" data-bs-theme="dark">
Before 4.2.0 that value was overwritten on load, and lost again the first time the OS preference changed.
The no-flash snippet in <head> also writes data-bs-theme, so it tags values it computed itself with data-lte-theme-resolved. That flag is how the module tells a theme you authored from one it derived — leave it alone; it is written and read for you.
Turning ColorMode off
If your application manages data-bs-theme itself, add data-lte-color-mode="off" to <html>:
<html lang="en" data-bs-theme="corporate" data-lte-color-mode="off">
AdminLTE then never writes data-bs-theme — not on load, not on a [data-bs-theme-value] toggle click, not when the OS preference changes — and the pre-paint snippet honours it too. The attribute is read live, so you can flip it at runtime.
This is also the escape hatch for custom Bootstrap theme names. ColorMode only understands light, dark and auto; if you ship your own theme names (corporate, high-contrast, …), turn the module off and drive the attribute yourself.
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.
Since 4.1.0 the roughly ten Bootstrap variable changes AdminLTE makes live in src/scss/_bootstrap-overrides.scss — the previous 1,700-line vendored copy of Bootstrap's variables file is gone. Nothing changes for your own theming: set your Bootstrap variable overrides before importing the AdminLTE source, exactly as before.