# AdminLTE 4 User Guide

**AdminLTE** is a free, MIT-licensed admin dashboard **template** built on **Bootstrap 5.3** with vanilla TypeScript — no jQuery. It gives you a responsive application shell (header, collapsible sidebar, content area, footer), light/dark/auto color modes, RTL support, seven ready-to-use JavaScript plugins, and a catalog of demo pages. You drop the markup into your own project and customize the parts you need.

This guide takes you from a blank HTML file to a working, customized dashboard. It is written for the people who *build* dashboards with AdminLTE: front-end developers, full-stack engineers wiring AdminLTE into a Laravel/Django/Rails view, and anyone integrating the template into an existing app.

> [!NOTE]
> AdminLTE is a **template, not a component library**. Bootstrap 5 supplies the components (buttons, cards, modals, forms); AdminLTE supplies the application shell that wraps them — the wrapper grid, the sidebar system, the color-mode plumbing, and the dashboard widgets. If you already know Bootstrap 5, you already know 80% of AdminLTE.

---

## Table of Contents

1. [Quick Start](#1-quick-start)
2. [Installation](#2-installation)
3. [The Layout System](#3-the-layout-system)
4. [Building the Sidebar Navigation](#4-building-the-sidebar-navigation)
5. [JavaScript Plugins](#5-javascript-plugins)
6. [Color Modes (Light / Dark / Auto)](#6-color-modes-light--dark--auto)
7. [Customizing the Look](#7-customizing-the-look)
8. [Common Recipes](#8-common-recipes)
9. [Adding Third-Party Widgets](#9-adding-third-party-widgets)
10. [Right-to-Left (RTL) Layouts](#10-right-to-left-rtl-layouts)
11. [Upgrading from AdminLTE 3](#11-upgrading-from-adminlte-3)
12. [Troubleshooting](#12-troubleshooting)
13. [FAQ](#13-faq)
14. [Reference](#14-reference)

---

## 1. Quick Start

AdminLTE is an HTML template — there is **no build step required to use it**. You can have a working dashboard in about ten minutes using a CDN, without npm, without a bundler, and without learning the project's internal Astro tooling.

### Step 1 — Add the CDN tags

Create a new HTML file. Put these four stylesheet tags in `<head>`. They pull AdminLTE, Bootstrap 5.3, OverlayScrollbars, and Bootstrap Icons from jsDelivr:

```html
<!-- In <head> -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/overlayscrollbars@2.11.0/styles/overlayscrollbars.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@4.0.0/dist/css/adminlte.min.css" />
```

Put these four script tags just before `</body>`:

```html
<!-- Before </body> -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/overlayscrollbars@2.11.0/browser/overlayscrollbars.browser.es6.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/admin-lte@4.0.0/dist/js/adminlte.min.js"></script>
```

> [!IMPORTANT]
> **Load order matters.** AdminLTE's CSS imports Bootstrap's CSS, and AdminLTE's JavaScript must load *after* Bootstrap and Popper. Keep the script order exactly as shown above or interactive behavior breaks silently.

### Step 2 — Drop in the minimum viable layout

This is the smallest page that still feels like AdminLTE — a header, a collapsible sidebar, and a content area. Place it inside `<body>`, between the CSS and JS tags:

```html
<body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
  <div class="app-wrapper">

    <!-- Header -->
    <nav class="app-header navbar navbar-expand bg-body">
      <div class="container-fluid">
        <ul class="navbar-nav">
          <li class="nav-item">
            <a class="nav-link" data-lte-toggle="sidebar" href="#" role="button">
              <i class="bi bi-list"></i>
            </a>
          </li>
        </ul>
      </div>
    </nav>

    <!-- Sidebar -->
    <aside class="app-sidebar bg-body-secondary shadow" data-bs-theme="dark">
      <div class="sidebar-brand">
        <a href="#" class="brand-link">
          <span class="brand-text fw-light">My Dashboard</span>
        </a>
      </div>
      <div class="sidebar-wrapper">
        <nav class="mt-2">
          <ul class="nav sidebar-menu flex-column" data-lte-toggle="treeview" role="menu">
            <li class="nav-item">
              <a href="#" class="nav-link active">
                <i class="nav-icon bi bi-speedometer"></i>
                <p>Dashboard</p>
              </a>
            </li>
            <li class="nav-item">
              <a href="#" class="nav-link">
                <i class="nav-icon bi bi-bar-chart"></i>
                <p>Reports</p>
              </a>
            </li>
          </ul>
        </nav>
      </div>
    </aside>

    <!-- Main content -->
    <main class="app-main">
      <div class="app-content-header">
        <div class="container-fluid">
          <h3 class="mb-0">Hello, AdminLTE</h3>
        </div>
      </div>
      <div class="app-content">
        <div class="container-fluid">
          <div class="card">
            <div class="card-header"><h5 class="card-title">Welcome</h5></div>
            <div class="card-body">
              Edit this file to start building your dashboard.
            </div>
          </div>
        </div>
      </div>
    </main>

  </div>
</body>
```

### Step 3 — Open it

Open the file in any modern browser. You should see a dashboard with a dark sidebar on a light page. Click the hamburger button (`bi-list`) at the top-left — the sidebar collapses. That is AdminLTE's **PushMenu** plugin working out of the box, wired entirely by the `data-lte-toggle="sidebar"` attribute. No JavaScript code of your own is needed.

> [!TIP]
> Every interactive feature in AdminLTE works the same way: add a `data-lte-toggle="..."` attribute to a trigger element and the bundled JavaScript wires it on page load. You only write JavaScript when you want *programmatic* control (see [Section 5](#5-javascript-plugins)).

### What's actually happening

Four classes do the heavy lifting:

| Class | What it does |
|---|---|
| `app-wrapper` | The CSS-grid root — defines the header / sidebar / main / footer regions |
| `app-header` | The top navbar; pinned to the top of the viewport when `layout-fixed` is on `<body>` |
| `app-sidebar` | The collapsible side rail; `sidebar-expand-lg` on `<body>` sets its breakpoint |
| `app-main` | The scrollable content region — everything else lives here |

The next sections explain each of these in full.

---

## 2. Installation

You have four installation paths. Pick the one that matches how you'll actually use AdminLTE.

| Path | Best for | Setup time |
|---|---|---|
| **CDN** | Prototyping, demos, simple sites | 1 minute |
| **npm + bundler** | Production apps, framework projects (Laravel, Symfony, Rails) | 5 minutes |
| **Build from source** | Customizing SCSS variables (sidebar width, breakpoints, brand colors) | 10 minutes |
| **Composer** | PHP projects that prefer Packagist over npm | 2 minutes |

### Prerequisites

| | Minimum | Recommended |
|---|---|---|
| Bootstrap | 5.3.0 | 5.3.8 (pinned in `package.json`) |
| Node.js | 18 LTS | 22 LTS (used by the project's CI) |
| Browser | Modern evergreen | Latest stable Chrome / Firefox / Safari / Edge |

> [!WARNING]
> AdminLTE 4 does **not** support Internet Explorer. If you need IE 11, stay on [AdminLTE 3](https://github.com/ColorlibHQ/AdminLTE/releases/tag/v3.2.0) or earlier — that constraint is not reversible in v4.

### CDN — the fastest path

Use the tags from [Quick Start](#1-quick-start). No install, no build step. Then copy any demo page's HTML into your own file and edit it. This is ideal for prototypes and simple internal tools.

### npm + bundler

Requires Node.js 18+ and a bundler (Vite, Webpack, Rollup, esbuild, etc.).

=== "npm"

    ```bash
    npm install admin-lte@4.0.0
    ```

=== "yarn"

    ```bash
    yarn add admin-lte@4.0.0
    ```

=== "pnpm"

    ```bash
    pnpm add admin-lte@4.0.0
    ```

Then import the CSS and JS in your app's entry point:

```js
// CSS
import "admin-lte/dist/css/adminlte.min.css"
// Plus Bootstrap and dependency CSS
import "bootstrap-icons/font/bootstrap-icons.css"
import "overlayscrollbars/styles/overlayscrollbars.css"

// JS
import "bootstrap"
import "admin-lte"
```

### Build from source

Choose this if you want to change SCSS variables — sidebar width, breakpoints, brand colors — which CSS custom properties can't reach.

```bash
git clone https://github.com/ColorlibHQ/AdminLTE.git
cd AdminLTE
npm install
npm run dev          # dev server with hot-reload at http://localhost:3000
npm run production   # one-off production build (clean + lint + compile)
```

The compiled output lands in `dist/`. Edit `src/scss/_variables.scss` or `src/scss/_bootstrap-variables.scss`, then rerun the build. See [Customizing the Look](#7-customizing-the-look) for which variables matter.

### Composer (PHP)

```bash
composer require "almasaeed2010/adminlte=4.0.0"
```

The Packagist package is published from the same source tree — you get the `dist/` folder ready to serve.

> [!NOTE]
> Whatever path you choose, the *runtime* artifacts are always the same two files: `dist/css/adminlte.min.css` and `dist/js/adminlte.min.js`, plus their Bootstrap and icon dependencies. AdminLTE doesn't bundle Bootstrap, Popper, OverlayScrollbars, or the icon font — you load those alongside it.

---

## 3. The Layout System

Every AdminLTE page follows the same blueprint: a wrapper containing a header, a sidebar, a main content area, and an optional footer.

### The four regions

```text
┌──────────────────────────────────────────┐
│  .app-wrapper                            │
│  ┌────────────────────────────────────┐  │
│  │  .app-header                       │  │ ← topbar / navbar
│  ├──────────┬─────────────────────────┤  │
│  │          │                         │  │
│  │  .app-   │   .app-main             │  │ ← page content
│  │ sidebar  │                         │  │
│  │          │                         │  │
│  ├──────────┴─────────────────────────┤  │
│  │  .app-footer (optional)            │  │
│  └────────────────────────────────────┘  │
└──────────────────────────────────────────┘
```

`.app-wrapper` is a CSS-grid container; the four children are grid areas positioned by their class. **Order inside `.app-wrapper` doesn't matter** — the grid template places each child by its class, so use whatever order reads cleanly.

### Minimal structure

```html
<body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
  <div class="app-wrapper">
    <nav class="app-header navbar navbar-expand bg-body">…</nav>
    <aside class="app-sidebar bg-body-secondary shadow" data-bs-theme="dark">…</aside>
    <main class="app-main">…</main>
    <footer class="app-footer">…</footer>
  </div>
</body>
```

### Region reference

#### `.app-wrapper`

The root container; defines the grid that positions header, sidebar, main, and footer. Always lives directly inside `<body>`. Add `.compact-mode` here (or anywhere up the tree) to reduce padding throughout the layout — useful for data-dense dashboards.

#### `.app-header`

The top bar. Use Bootstrap's navbar classes (`navbar navbar-expand bg-body`) for the visuals. This is where the sidebar toggle button lives:

```html
<a class="nav-link" data-lte-toggle="sidebar" href="#" role="button">
  <i class="bi bi-list"></i>
</a>
```

By default the header scrolls with the page. Add `.fixed-header` to `<body>` to pin it.

#### `.app-sidebar`

The side rail. It always contains two children — `.sidebar-brand` (logo / app name) and `.sidebar-wrapper` (the scrollable nav region):

```html
<aside class="app-sidebar bg-body-secondary shadow" data-bs-theme="dark">
  <div class="sidebar-brand">
    <a href="/" class="brand-link">
      <span class="brand-text fw-light">My Dashboard</span>
    </a>
  </div>
  <div class="sidebar-wrapper">
    <nav class="mt-2">
      <ul class="nav sidebar-menu flex-column" data-lte-toggle="treeview" role="menu">
        <!-- nav items -->
      </ul>
    </nav>
  </div>
</aside>
```

The `data-bs-theme="dark"` attribute on the sidebar gives the dark-sidebar-on-light-page look from the demos. Drop it for a light sidebar.

#### `.app-main`

The page content region. It almost always contains two children:

```html
<main class="app-main">
  <div class="app-content-header">
    <!-- page title + breadcrumb -->
  </div>
  <div class="app-content">
    <!-- page body: cards, tables, charts, etc. -->
  </div>
</main>
```

`.app-content-header` is the title strip — typically an `<h3>` and a breadcrumb. `.app-content` is everything below it. Wrap the children of both in `.container-fluid` (or `.container` for capped width).

#### `.app-footer` (optional)

A footer strip at the bottom of the wrapper:

```html
<footer class="app-footer">
  <div class="float-end d-none d-sm-inline">Version 4.0.0</div>
  <strong>&copy; 2026 Your Company</strong>
</footer>
```

Add `.fixed-footer` to `<body>` to pin it.

### Body-level layout modifiers

Layout behavior is controlled mostly by classes on `<body>`, not on `.app-wrapper`:

| Class | What it does |
|---|---|
| `layout-fixed` | Sidebar gets its own scrollbar; only `.app-main` scrolls with the page |
| `fixed-header` | Sticky `.app-header`; the sidebar also pins (new in v4.0.0) |
| `fixed-footer` | Sticky `.app-footer` at the bottom |
| `sidebar-expand-{sm,md,lg,xl,xxl}` | Breakpoint at which the sidebar switches from off-canvas (mobile) to inline (desktop). `lg` is the demo default. |
| `sidebar-mini` | Collapses the sidebar to an icon-only mini state |
| `sidebar-mini sidebar-collapse` | Starts the page with the mini sidebar already collapsed |
| `sidebar-without-hover` | Disables the auto-expand-on-hover behavior for the mini sidebar |
| `layout-rtl` | Mirrors the layout for right-to-left languages (also needs `.rtl.css` + `dir="rtl"`; see [Section 10](#10-right-to-left-rtl-layouts)) |

These compose. The demo dashboards use `class="layout-fixed sidebar-expand-lg bg-body-tertiary"`.

### Responsive behavior

At and above your `sidebar-expand-*` breakpoint the sidebar is **inline** — visible in the grid alongside `.app-main`. Below the breakpoint it becomes **off-canvas** — hidden, and slid in from the left when the user taps the hamburger. The [PushMenu plugin](#51-pushmenu--the-sidebar) manages this automatically.

> [!TIP]
> The eleven prebuilt layout combinations live in the demo's `layout/` folder (`fixed-sidebar`, `fixed-header`, `fixed-footer`, `fixed-complete`, `unfixed-sidebar`, `sidebar-mini`, `collapsed-sidebar`, `collapsed-sidebar-without-hover`, `layout-custom-area`, `logo-switch`, `layout-rtl`). Open the matching demo page to see each modifier in isolation.

### Common pitfalls

> [!CAUTION]
> - **Forgetting `.container-fluid` inside `.app-content`** — content lands flush against the screen edge. Always wrap content in a container.
> - **Mixing `.app-*` and `.main-*` classes** — `.main-*` is the AdminLTE 3 naming. Don't combine them (see [Section 11](#11-upgrading-from-adminlte-3)).
> - **Using `position: fixed` on something inside `.app-main`** — with `layout-fixed`, `.app-main` already has `overflow: auto`. Use sticky positioning instead.
> - **Hardcoding sidebar width in custom CSS** — the width is the SCSS variable `$lte-sidebar-width`. If you need it in JavaScript, read the computed width of the `.app-sidebar` element.

---

## 4. Building the Sidebar Navigation

The sidebar's nav menu is powered by the **Treeview** plugin. Add `data-lte-toggle="treeview"` to the parent `<ul>`, mark items with `.nav-item`, and put any submenu in a nested `<ul class="nav-treeview">`.

### A single-level menu

```html
<ul class="nav sidebar-menu flex-column" data-lte-toggle="treeview" role="menu">
  <li class="nav-item">
    <a class="nav-link active" href="/">
      <i class="nav-icon bi bi-speedometer"></i>
      <p>Dashboard</p>
    </a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="/reports">
      <i class="nav-icon bi bi-bar-chart"></i>
      <p>Reports</p>
    </a>
  </li>
</ul>
```

Mark the current page with `.active` on its `.nav-link`. For server-rendered apps, add it in your template; for SPAs, toggle it on route change (see [the recipe below](#highlighting-the-active-sidebar-item-from-javascript)).

### A multi-level (collapsible) menu

Add a `.nav-arrow` chevron and a nested `.nav-treeview` list. The chevron rotates when the submenu opens:

```html
<li class="nav-item">
  <a href="#" class="nav-link">
    <i class="nav-icon bi bi-folder"></i>
    <p>
      Reports
      <i class="nav-arrow bi bi-chevron-right"></i>
    </p>
  </a>
  <ul class="nav nav-treeview">
    <li class="nav-item">
      <a href="#" class="nav-link"><i class="nav-icon bi bi-circle"></i><p>Sales</p></a>
    </li>
    <li class="nav-item">
      <a href="#" class="nav-link"><i class="nav-icon bi bi-circle"></i><p>Traffic</p></a>
    </li>
  </ul>
</li>
```

To start with a submenu already expanded, add `menu-open` to its `.nav-item`.

### Section headers and badges

Group sections with `.nav-header` and show counts with a Bootstrap badge:

```html
<li class="nav-header">REPORTS</li>
<li class="nav-item">
  <a href="#" class="nav-link">
    <i class="nav-icon bi bi-bar-chart"></i>
    <p>Sales</p>
    <span class="badge text-bg-danger ms-auto">3</span>
  </a>
</li>
```

### Accordion vs. multi-open

By default the treeview is an **accordion** — opening one submenu closes its siblings. To allow multiple submenus open at once, set `data-accordion="false"` on the parent `<ul>`:

```html
<ul class="nav sidebar-menu" data-lte-toggle="treeview" data-accordion="false">
  …
</ul>
```

You can also tune the slide animation. Default is `300` ms; set `0` to disable:

```html
<ul class="nav sidebar-menu" data-lte-toggle="treeview" data-animation-speed="150">
  …
</ul>
```

| Attribute | Default | Description |
|---|---|---|
| `data-accordion` | `true` | Close other open submenus when one opens |
| `data-animation-speed` | `300` | Slide animation duration in milliseconds (`0` disables) |

---

## 5. JavaScript Plugins

AdminLTE ships **seven** JavaScript plugins as a single bundle (`adminlte.js`). Each one can be triggered declaratively with a `data-lte-*` attribute (no code needed) and also exposes a programmatic API for code-driven control.

| Plugin | Data API trigger | What it does |
|---|---|---|
| **PushMenu** | `data-lte-toggle="sidebar"` | Collapse / expand the sidebar |
| **Treeview** | `data-lte-toggle="treeview"` (on the parent menu) | Expand / collapse sidebar submenus |
| **CardWidget** | `data-lte-toggle="card-collapse"`, `card-remove`, `card-maximize` | Collapse, remove, or maximize a card |
| **DirectChat** | `data-lte-toggle="chat-pane"` | Toggle the contacts pane of a chat widget |
| **FullScreen** | `data-lte-toggle="fullscreen"` | Toggle native browser fullscreen |
| **Layout** | (auto-applied to `<body>`) | Manage transitions during resize; signal "app loaded" |
| **AccessibilityManager** | (auto-initialized) | WCAG 2.1 AA features (see [Section 5.7](#57-accessibilitymanager)) |

### Two ways to use a plugin

**1. Data API (declarative)** — for most pages this is all you need. Drop the right attribute on the trigger and the bundle wires it up on `DOMContentLoaded`:

```html
<button data-lte-toggle="sidebar">☰</button>
```

**2. Programmatic (imperative)** — when you need to control a plugin from your own code (e.g. open the sidebar after login, expand a card on a route change), instantiate the class directly. All seven are exported from the package root:

```js
// ESM (bundler import)
import { PushMenu, CardWidget, Treeview } from "admin-lte"

new PushMenu(document.body, {}).expand()

const card = document.querySelector("#chart-card")
new CardWidget(card, {}).maximize()
```

Or via the global the UMD bundle assigns:

```html
<script>
  // The bundle assigns to window.adminlte
  new adminlte.PushMenu(document.body, {}).expand()
</script>
```

> [!NOTE]
> The bundle attaches all data-API listeners on `DOMContentLoaded`. PushMenu, CardWidget, and Treeview use event delegation, so dynamically-injected elements still work after page load.

### Listening to plugin events

Every plugin fires an event on the element it operates on. All events bubble, so you can listen on `document`:

```js
document.addEventListener("expanded.lte.card-widget", (e) => {
  console.log("Card expanded:", e.target)
})

document.addEventListener("open.lte.push-menu", () => {
  // user opened the sidebar — e.g. an analytics call
})
```

| Plugin | Event | Fired when |
|---|---|---|
| PushMenu | `open.lte.push-menu` | Sidebar expanded |
| PushMenu | `collapse.lte.push-menu` | Sidebar collapsed |
| Treeview | `expanded.lte.treeview` | Submenu opened |
| Treeview | `collapsed.lte.treeview` | Submenu closed |
| Treeview | `load.lte.treeview` | A pre-opened submenu was detected on page load |
| CardWidget | `expanded.lte.card-widget` | Card body expanded |
| CardWidget | `collapsed.lte.card-widget` | Card body collapsed |
| CardWidget | `remove.lte.card-widget` | Card removed |
| CardWidget | `maximized.lte.card-widget` | Card maximized |
| CardWidget | `minimized.lte.card-widget` | Card restored from maximized |
| DirectChat | `expanded.lte.direct-chat` | Contacts pane opened |
| DirectChat | `collapsed.lte.direct-chat` | Contacts pane closed |
| FullScreen | `maximized.lte.fullscreen` | Entered fullscreen |
| FullScreen | `minimized.lte.fullscreen` | Exited fullscreen |

### 5.1 PushMenu — the sidebar

Toggles the sidebar between expanded and collapsed, handling responsive behavior, optional localStorage persistence, and the mobile overlay.

**Data API:**

```html
<a href="#" data-lte-toggle="sidebar">Toggle Sidebar</a>
```

**Configuration** (data attributes on the `.app-sidebar` element):

| Attribute | Default | Description |
|---|---|---|
| `data-sidebar-breakpoint` | `992` | Screen width (px) below which the sidebar is treated as mobile |
| `data-enable-persistence` | `false` | Save sidebar state to localStorage across page loads |

```html
<aside class="app-sidebar" data-enable-persistence="true" data-sidebar-breakpoint="768">
  …
</aside>
```

When persistence is on, the plugin reads/writes the localStorage key `lte.sidebar.state`, storing either `"sidebar-open"` or `"sidebar-collapse"`.

**Programmatic methods:**

| Method | Description |
|---|---|
| `toggle()` | Expand or collapse based on current state (persists if enabled) |
| `expand()` | Expand the sidebar; fires `open.lte.push-menu` |
| `collapse()` | Collapse the sidebar; fires `collapse.lte.push-menu` |
| `isCollapsed()` | `true` if `<body>` has `sidebar-collapse` |
| `isExplicitlyOpen()` | `true` if `<body>` has `sidebar-open` (mobile, user-opened) |
| `isMiniMode()` | `true` if `<body>` has `sidebar-mini` |
| `isMobileSize()` | `true` if `window.innerWidth ≤ sidebarBreakpoint` |
| `saveSidebarState(state)` | Manually persist `"sidebar-open"` or `"sidebar-collapse"` |
| `loadSidebarState()` | Read and apply persisted state |
| `clearSidebarState()` | Remove persisted state |
| `updateStateByResponsiveLogic()` | Recompute state from viewport size (auto-called on resize) |

### 5.2 Treeview — sidebar submenus

Covered in detail in [Section 4](#4-building-the-sidebar-navigation). Programmatic API:

```js
import { Treeview } from "admin-lte"

const item = document.querySelector(".sidebar-menu .nav-item")
const tv = new Treeview(item, { accordion: true, animationSpeed: 300 })

tv.open()    // expand this item's submenu
tv.close()   // collapse it
tv.toggle()  // toggle
```

### 5.3 CardWidget — collapsible / removable / maximizable cards

Adds collapse, remove, and maximize controls to any Bootstrap `.card`. Put the trigger buttons inside `.card-tools` in the card header:

```html
<div class="card">
  <div class="card-header">
    <h3 class="card-title">Card Title</h3>
    <div class="card-tools">
      <button type="button" class="btn btn-tool" data-lte-toggle="card-collapse" title="Collapse">
        <i data-lte-icon="expand" class="bi bi-plus-lg"></i>
        <i data-lte-icon="collapse" class="bi bi-dash-lg"></i>
      </button>
      <button type="button" class="btn btn-tool" data-lte-toggle="card-maximize" title="Maximize">
        <i class="bi bi-fullscreen"></i>
      </button>
      <button type="button" class="btn btn-tool" data-lte-toggle="card-remove" title="Remove">
        <i class="bi bi-x-lg"></i>
      </button>
    </div>
  </div>
  <div class="card-body">Card content here.</div>
  <div class="card-footer">Card footer</div>
</div>
```

**Programmatic methods:** `collapse()`, `expand()`, `toggle()`, `maximize()`, `minimize()`, `toggleMaximize()`, `remove()`. Instantiate with an optional `animationSpeed` (default `500` ms):

```js
import { CardWidget } from "admin-lte"
const widget = new CardWidget(document.querySelector("#chart-card"), { animationSpeed: 250 })
widget.maximize()
```

> [!NOTE]
> Nested cards are handled correctly — collapsing a parent card does not affect child cards. Maximizing a card also adds `maximized-card` to `<html>` so global styles can react. A card that was collapsed before you maximize it remembers that state (tracked via the `was-collapsed` class).

### 5.4 DirectChat — chat contacts pane

Toggles a contacts pane inside a `.direct-chat` card. Add `data-lte-toggle="chat-pane"` to a button inside the chat card:

```html
<div class="card direct-chat">
  <div class="card-header">
    <h3 class="card-title">Direct Chat</h3>
    <div class="card-tools">
      <button type="button" class="btn btn-tool" data-lte-toggle="chat-pane" title="Toggle Contacts">
        <i class="bi bi-people-fill"></i>
      </button>
    </div>
  </div>
  <div class="card-body">
    <div class="direct-chat-messages"><!-- messages --></div>
    <div class="direct-chat-contacts"><!-- contacts --></div>
  </div>
</div>
```

Programmatic: `new DirectChat(element).toggle()`. The constructor takes no config object — pass only the `.direct-chat` element. See the [direct-chat recipe](#a-direct-chat-widget) for a fuller example.

### 5.5 FullScreen — native browser fullscreen

Toggles the whole page into the browser's native fullscreen mode. Use `data-lte-icon="maximize"` and `data-lte-icon="minimize"` on the child icons so the right one shows:

```html
<button type="button" class="btn" data-lte-toggle="fullscreen">
  <i data-lte-icon="maximize" class="bi bi-arrows-fullscreen"></i>
  <i data-lte-icon="minimize" class="bi bi-fullscreen-exit d-none"></i>
</button>
```

Programmatic: `new FullScreen(button).toggleFullScreen()`.

> [!NOTE]
> Fullscreen targets `<html>`, so the entire page goes fullscreen. To make just one card fill the screen, use the CardWidget `maximize()` method instead. The plugin checks `document.fullscreenEnabled` before acting, and toggles the icons via Bootstrap's `.d-none` utility so it works with any icon library (Bootstrap Icons, FontAwesome, Lucide, etc.).

### 5.6 Layout — transitions and load signal

This plugin self-initializes on `<body>` — there's nothing to wire up. It does two things automatically:

1. **Holds transitions during resize** — adds `hold-transition` to `<body>` briefly so the sidebar/navbar/content don't animate awkwardly while the window is being resized.
2. **Signals app-loaded** — adds `app-loaded` to `<body>` shortly after `DOMContentLoaded`. Use this in CSS to defer entrance animations until the layout is ready:

```css
/* Only animate after the app is loaded */
body.app-loaded .app-sidebar {
  transition: width 0.3s ease;
}
```

The single public method, `holdTransition(time)`, lets you suppress transitions around your own DOM changes:

```js
import { Layout } from "admin-lte"
const layout = new Layout(document.body)
layout.holdTransition(200)
document.body.classList.toggle("sidebar-mini")
```

### 5.7 AccessibilityManager

AdminLTE auto-initializes a set of WCAG 2.1 AA features when it loads — you get them for free. They are configured in the bundle with everything enabled:

| Feature | WCAG criterion | What it does |
|---|---|---|
| Skip links | 2.4.1 | Adds "Skip to main content" / "Skip to navigation" links |
| Focus management | 2.4.3, 2.4.7 | Tab cycling, focus trapping in modals, focus restoration |
| Keyboard navigation | 2.1.1 | Arrow-key navigation in menus; Enter/Space for custom `role="button"` elements |
| Reduced motion | 2.3.3 | Respects the OS `prefers-reduced-motion` setting by disabling animations |
| Live announcements | 4.1.3 | Announces error and success messages to screen readers |
| Error identification | 3.3.1, 3.3.2 | Auto-labels unlabelled inputs; announces form-validation errors |
| Table accessibility | 1.3.1 | Adds `scope` attributes to table headers automatically |

If you need to control it, the helper `initAccessibility()` and the manager's public API are exported:

```js
import { initAccessibility } from "admin-lte"

const a11y = initAccessibility({
  announcements: true,
  skipLinks: true,
  focusManagement: true,
  keyboardNavigation: true,
  reducedMotion: true
})

a11y.announce("Item saved successfully", "polite")   // screen-reader announcement
a11y.focusElement("#my-input")                        // move focus + scroll into view
a11y.trapFocus(document.querySelector(".my-dialog"))  // trap Tab inside a container
a11y.addLandmarks()                                   // add ARIA landmark roles
```

Set any feature flag to `false` to disable it.

> [!TIP]
> A standalone `accessibilityUtils` object is also exported, with `checkColorContrast(fg, bg)` (returns `{ ratio, passes }` against the 4.5:1 threshold), `isFocusable(element)`, and `generateId(prefix)` helpers for building accessible UI of your own.

---

## 6. Color Modes (Light / Dark / Auto)

AdminLTE follows Bootstrap 5.3's native theming. The active theme is set by the **`data-bs-theme`** attribute — apply it to `<html>`, `<body>`, or any single element:

```html
<!-- Whole page dark -->
<html data-bs-theme="dark">

<!-- Just the sidebar dark, page light (the demo look) -->
<aside class="app-sidebar" data-bs-theme="dark">…</aside>
```

### A working light/dark/auto toggle

AdminLTE's demos include a dropdown toggle in the topbar that switches the attribute and remembers the choice. The markup uses `data-bs-theme-value` on each option:

```html
<li class="nav-item dropdown">
  <button class="btn btn-link nav-link dropdown-toggle d-flex align-items-center"
          id="bd-theme" type="button" data-bs-toggle="dropdown" aria-expanded="false">
    <span class="theme-icon-active"><i class="my-1"></i></span>
    <span class="d-lg-none ms-2" id="bd-theme-text">Toggle theme</span>
  </button>
  <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="bd-theme-text">
    <li><button type="button" class="dropdown-item d-flex align-items-center active" 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 d-flex align-items-center" 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 d-flex align-items-center" 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 accompanying script reads the preferred theme (stored choice → OS preference), writes `data-bs-theme` on `<html>`, and persists the user's selection to `localStorage`:

```js
(() => {
  "use strict";
  const storedTheme = localStorage.getItem("theme");

  const getPreferredTheme = () =>
    storedTheme ||
    (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");

  const setTheme = (theme) => {
    if (theme === "auto" && window.matchMedia("(prefers-color-scheme: dark)").matches) {
      document.documentElement.setAttribute("data-bs-theme", "dark");
    } else {
      document.documentElement.setAttribute("data-bs-theme", theme);
    }
  };

  setTheme(getPreferredTheme());

  window.addEventListener("DOMContentLoaded", () => {
    for (const toggle of document.querySelectorAll("[data-bs-theme-value]")) {
      toggle.addEventListener("click", () => {
        const theme = toggle.getAttribute("data-bs-theme-value");
        localStorage.setItem("theme", theme);
        setTheme(theme);
      });
    }
  });
})();
```

> [!NOTE]
> This color-mode toggle is a **copy-paste snippet**, not part of the `adminlte.js` bundle. The snippet persists the choice under the localStorage key `theme`. If you write your own toggle, make sure it both sets `data-bs-theme` and writes to localStorage.

> [!TIP]
> The **Auto** option follows the OS preference live: the script listens for changes to `prefers-color-scheme` and re-applies the theme when the user switches their system between light and dark.

---

## 7. Customizing the Look

You can retheme AdminLTE two ways:

1. **CSS custom properties** — quick, no build step, works on the prebuilt CSS.
2. **SCSS variables** — requires a build, but lets you change *anything*, including breakpoints and sidebar width.

### Quick wins with CSS variables

Bootstrap and AdminLTE expose dozens of custom properties on `:root`. Override them in your own stylesheet — no rebuild needed:

```css
:root,
[data-bs-theme="light"] {
  --bs-primary: #6610f2;          /* swap Bootstrap blue for purple */
  --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)`, so it picks this up automatically.

| Variable | Default | Controls |
|---|---|---|
| `--bs-primary` | `#0d6efd` | Brand color; active sidebar items, primary buttons |
| `--bs-body-bg` | `#fff` / `#212529` (dark) | Page background |
| `--bs-body-color` | `#212529` | Default text color |
| `--bs-border-color` | `#dee2e6` | Hairline borders |
| `--bs-border-radius` | `.375rem` | Default radius for cards, buttons, inputs |
| `--bs-font-sans-serif` | `system-ui, …` | Default font stack |

### SCSS variables (build from source)

When building from source, override variables *before* importing AdminLTE. All AdminLTE/Bootstrap variables use `!default`, so the first declaration wins:

```scss
// my-theme.scss

// 1. Your overrides FIRST
$primary:           #6610f2;
$lte-sidebar-width: 280px;
$border-radius:     .25rem;

// 2. Then import AdminLTE (which imports Bootstrap)
@import "node_modules/admin-lte/src/scss/adminlte";
```

The most-overridden AdminLTE variables and their defaults:

```scss
// Sidebar
$lte-sidebar-width:        250px;   // collapses to a ~70px mini state
$lte-sidebar-breakpoint:   lg;      // breakpoint where the sidebar goes off-canvas
$lte-sidebar-padding-x:    .5rem;
$lte-sidebar-padding-y:    .5rem;

// Transitions
$lte-transition-speed:     .3s;
$lte-transition-fn:        ease-in-out;
```

> [!IMPORTANT]
> Sidebar width and breakpoints are **SCSS-only** — there are no CSS custom properties for them. If you need to change those, you must build from source. Use CSS variables for everything they cover (colors, radius, fonts), because those survive AdminLTE upgrades without merge conflicts.

### When to use which

| Customization | CSS variables | SCSS rebuild |
|---|---|---|
| Brand colors | ✅ | ✅ |
| Body / text colors | ✅ | ✅ |
| Border radius | ✅ | ✅ |
| Sidebar width | ❌ | ✅ |
| Breakpoint changes | ❌ | ✅ |
| Spacing scale | ❌ | ✅ |

### Sidebar branding

`.sidebar-brand` accepts text, an image, or both:

```html
<div class="sidebar-brand">
  <a href="/" class="brand-link">
    <img src="/assets/logo.svg" alt="Logo" class="brand-image opacity-75 shadow" />
    <span class="brand-text fw-light">My Dashboard</span>
  </a>
</div>
```

### Compact mode

Add `.compact-mode` to `.app-wrapper` (or higher) to tighten padding across the whole layout — handy for data-dense dashboards:

```html
<div class="app-wrapper compact-mode">…</div>
```

---

## 8. Common Recipes

Copy-paste patterns that show up in every admin dashboard. Each assumes you have a working AdminLTE page from [Quick Start](#1-quick-start).

### A KPI card (small-box)

The bright stat boxes at the top of most dashboards. Swap `text-bg-primary` for `text-bg-success`, `text-bg-warning`, `text-bg-danger`, etc.

```html
<div class="row g-3">
  <div class="col-lg-3 col-6">
    <div class="small-box text-bg-primary">
      <div class="inner">
        <h3>150</h3>
        <p>New orders</p>
      </div>
      <i class="bi bi-cart small-box-icon" aria-hidden="true"></i>
      <a href="#" class="small-box-footer link-light">
        More info <i class="bi bi-arrow-right"></i>
      </a>
    </div>
  </div>
  <!-- repeat with other values + colors -->
</div>
```

### A page-title + breadcrumb row

The standard `.app-content-header` pattern:

```html
<div class="app-content-header">
  <div class="container-fluid">
    <div class="row">
      <div class="col-sm-6"><h3 class="mb-0">Page Title</h3></div>
      <div class="col-sm-6">
        <ol class="breadcrumb float-sm-end">
          <li class="breadcrumb-item"><a href="/">Home</a></li>
          <li class="breadcrumb-item active" aria-current="page">Current page</li>
        </ol>
      </div>
    </div>
  </div>
</div>
```

### A user dropdown in the topbar

Put this inside `<ul class="navbar-nav ms-auto">` in `.app-header`:

```html
<li class="nav-item dropdown user-menu">
  <a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">
    <span class="d-none d-md-inline me-1">Jane Doe</span>
    <i class="bi bi-person-circle fs-5"></i>
  </a>
  <ul class="dropdown-menu dropdown-menu-end">
    <li><a href="/profile" class="dropdown-item"><i class="bi bi-person me-2"></i>Profile</a></li>
    <li><a href="/settings" class="dropdown-item"><i class="bi bi-gear me-2"></i>Settings</a></li>
    <li><hr class="dropdown-divider"></li>
    <li><a href="/logout" class="dropdown-item text-danger"><i class="bi bi-box-arrow-right me-2"></i>Sign out</a></li>
  </ul>
</li>
```

### A responsive data table

Wrap the table in `.table-responsive` so it scrolls horizontally on narrow screens instead of overflowing the card:

```html
<div class="card">
  <div class="card-header"><h3 class="card-title">Recent orders</h3></div>
  <div class="card-body p-0">
    <div class="table-responsive">
      <table class="table table-striped align-middle mb-0">
        <thead>
          <tr><th>#</th><th>Customer</th><th>Total</th><th>Status</th></tr>
        </thead>
        <tbody><!-- rows --></tbody>
      </table>
    </div>
  </div>
</div>
```

For sorting/filtering/pagination, use [Tabulator](#9-adding-third-party-widgets).

### A direct-chat widget

```html
<div class="card direct-chat direct-chat-primary">
  <div class="card-header">
    <h3 class="card-title">Direct Chat</h3>
    <div class="card-tools">
      <span class="badge text-bg-primary">3 new</span>
      <button type="button" class="btn btn-tool" data-lte-toggle="chat-pane" title="Contacts">
        <i class="bi bi-people-fill"></i>
      </button>
    </div>
  </div>
  <div class="card-body">
    <div class="direct-chat-messages">
      <div class="direct-chat-msg">
        <div class="direct-chat-infos clearfix">
          <span class="direct-chat-name float-start">Olivia</span>
          <span class="direct-chat-timestamp float-end">12:05</span>
        </div>
        <img class="direct-chat-img" src="/avatars/olivia.jpg" alt="" />
        <div class="direct-chat-text">Hey, ready for the review?</div>
      </div>
    </div>
    <div class="direct-chat-contacts"><!-- contacts, shown when toggled --></div>
  </div>
</div>
```

### Highlighting the active sidebar item from JavaScript

For a single rendered sidebar with client-side route highlighting:

```js
function setActiveSidebarItem(href) {
  document.querySelectorAll(".sidebar-menu .nav-link").forEach((el) => {
    el.classList.toggle("active", el.getAttribute("href") === href)
  })
}

setActiveSidebarItem(window.location.pathname)  // call on every route change
```

### Refitting a chart when the sidebar collapses

Charts read their container width on init, so re-measure them after the sidebar transition finishes:

```js
document.addEventListener("collapse.lte.push-menu", () => {
  setTimeout(() => chart.updateOptions({}), 350)  // ~300ms transition + buffer
})
document.addEventListener("open.lte.push-menu", () => {
  setTimeout(() => chart.updateOptions({}), 350)
})
```

The same pattern works for Tabulator, FullCalendar, and any width-sensitive widget.

---

## 9. Adding Third-Party Widgets

AdminLTE deliberately ships a **lean dependency tree** — it doesn't bundle date pickers, charts, or rich-text editors. Instead, drop in best-in-class third-party libraries when you need them. Every library below is MIT-licensed, actively maintained, and jQuery-free.

| Need | Library | One-line wire-up |
|---|---|---|
| Date / time picker | [Flatpickr](https://flatpickr.js.org/) | `flatpickr("#datepicker", { dateFormat: "Y-m-d" })` |
| Multi-select / tags | [Tom Select](https://tom-select.js.org/) | `new TomSelect("#tags", { plugins: ["remove_button"] })` |
| Range slider | [noUiSlider](https://refreshless.com/nouislider/) | `noUiSlider.create(el, { start: [20, 80], range: { min: 0, max: 100 } })` |
| Color picker | [Pickr](https://github.com/Simonwep/pickr) | `Pickr.create({ el: "#color-picker", theme: "classic" })` |
| Input mask | [IMask](https://imask.js.org/) | `IMask(el, { mask: "(000) 000-0000" })` |
| File upload | [Dropzone](https://www.dropzone.dev/) / [FilePond](https://pqina.nl/filepond/) | `new Dropzone("#uploader", { maxFilesize: 5 })` |
| Rich text | [Quill](https://quilljs.com/) | `new Quill("#editor", { theme: "snow" })` |
| Markdown | [EasyMDE](https://github.com/Ionaru/easy-markdown-editor) | `new EasyMDE({ element: el })` |
| Charts | [ApexCharts](https://apexcharts.com/) / [Chart.js](https://www.chartjs.org/) | `new ApexCharts(el, options).render()` |
| Data tables | [Tabulator](https://tabulator.info/) | `new Tabulator("#table", { layout: "fitColumns" })` |
| Calendar | [FullCalendar](https://fullcalendar.io/) | `new FullCalendar.Calendar(el, { initialView: "dayGridMonth" }).render()` |
| Drag & drop | [SortableJS](https://sortablejs.github.io/Sortable/) | `new Sortable(el, { animation: 150 })` |
| Lightbox | [GLightbox](https://biati-digital.github.io/glightbox/) | `GLightbox()` |

The v4 demo pages already use several of these: ApexCharts (dashboard), Tabulator (`tables/data`), FullCalendar (`pages/calendar`), and SortableJS (`pages/kanban`). Each library ships a Bootstrap 5 theme where relevant (Tom Select, Tabulator), so it matches AdminLTE's form controls.

### Icon libraries

AdminLTE ships **Bootstrap Icons** by default (~1,900 SVG glyphs, MIT). All of these coexist with AdminLTE if you want more — just load the font and change the `<i>` classes:

| Library | Icons | Notes |
|---|---|---|
| [Bootstrap Icons](https://icons.getbootstrap.com/) | 1,900+ | Default, SVG, MIT |
| [Lucide](https://lucide.dev/) | 1,500+ | ISC, very consistent style |
| [Tabler Icons](https://tabler.io/icons) | 5,800+ | MIT, broadest free set |
| [Material Symbols](https://fonts.google.com/icons) | 3,500+ | Apache 2.0, variable font |
| [FontAwesome Free](https://fontawesome.com/icons) | 2,000+ | Font-based |
| [Phosphor](https://phosphoricons.com/) | 6,000+ | MIT, six weights |

> [!TIP]
> For production, prefer npm + a bundler (for tree-shaking and version locking) or self-hosting the minified files over relying on a public CDN long-term. Pin a specific version in any CDN URL so snippets stay reproducible.

---

## 10. Right-to-Left (RTL) Layouts

AdminLTE ships a pre-built RTL stylesheet for Arabic, Hebrew, Persian, Urdu, and other right-to-left scripts. Every release publishes RTL variants of every CSS file:

```text
dist/css/adminlte.css           ← LTR (default)
dist/css/adminlte.rtl.css       ← RTL
dist/css/adminlte.min.css
dist/css/adminlte.rtl.min.css
```

To switch a page to RTL, load the `.rtl` stylesheet and set `dir="rtl"` on `<html>`:

```html
<html lang="ar" dir="rtl">
  <head>
    <link rel="stylesheet" href="dist/css/adminlte.rtl.min.css" />
  </head>
  <body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
    <!-- same markup as LTR -->
  </body>
</html>
```

That's all — the sidebar docks to the right, scrollbars flip, and Bootstrap's `.ms-*` / `.float-start` utilities mirror automatically (setting `dir="rtl"` triggers Bootstrap 5.3's logical-property pathway). For a mixed-language page, scope `dir="rtl"` to a single subtree instead of swapping the whole stylesheet.

| Flips automatically | Does **not** flip |
|---|---|
| `margin-left` / `padding-left` → right | Directional icons (`bi-arrow-right` etc.) — swap manually |
| `left` / `right` positioning | Inline images, charts, third-party widgets (check each for an RTL option) |
| `text-align: left` / `right` | Numbers (digits stay LTR — standard Unicode behavior) |
| Sidebar position; dropdown open direction | |

> [!CAUTION]
> The minified RTL build is named `adminlte.rtl.min.css`, **not** `adminlte.min.rtl.css` — filename order matters. And don't load both LTR and RTL stylesheets on the same page; pick one.

---

## 11. Upgrading from AdminLTE 3

AdminLTE 4 is a ground-up rewrite on Bootstrap 5.3 with no jQuery. If you're upgrading a v3 project, these are the changes most likely to bite.

| | AdminLTE 3 | AdminLTE 4 |
|---|---|---|
| CSS framework | Bootstrap 4.6 | **Bootstrap 5.3** |
| JavaScript | jQuery + vanilla | **Vanilla TypeScript only** |
| Dark mode | `.dark-mode` body class | **`data-bs-theme` attribute** |
| RTL | Separate build | **Auto-generated via rtlcss** |
| Browsers | IE 11 forks available | Modern evergreen only |
| Icons | FontAwesome | **Bootstrap Icons** |

### Class renames (the bulk of the work)

| AdminLTE 3 | AdminLTE 4 |
|---|---|
| `.wrapper` | `.app-wrapper` |
| `.main-header` | `.app-header` |
| `.main-sidebar` | `.app-sidebar` |
| `.content-wrapper` | `.app-main` |
| `.main-footer` | `.app-footer` |
| `.content-header` | `.app-content-header` |
| `.content` | `.app-content` |
| `data-widget="pushmenu"` | `data-lte-toggle="sidebar"` |
| `data-widget="treeview"` | `data-lte-toggle="treeview"` |
| `data-widget="card-widget"` | `data-lte-toggle="card-collapse"` / `card-remove` / `card-maximize` |
| `data-widget="fullscreen"` | `data-lte-toggle="fullscreen"` |
| `.dark-mode` (on `<body>`) | `data-bs-theme="dark"` |

You'll also need the Bootstrap 4→5 utility renames (`data-toggle` → `data-bs-toggle`, `.ml-*`/`.mr-*` → `.ms-*`/`.me-*`, `.float-left`/`.float-right` → `.float-start`/`.float-end`, `.font-weight-*` → `.fw-*`, etc.) — these come from Bootstrap, not AdminLTE, and a regex find-replace handles most of them.

### jQuery removal

AdminLTE 3 plugin calls like `$('.sidebar').PushMenu()` no longer exist. Either use the data API (which does it for you), or import the plugin class:

```js
import { PushMenu } from "admin-lte"
new PushMenu(document.querySelector(".sidebar-toggle"), {}).toggle()
```

### Suggested order

1. **Branch first** — keep `master` on v3 until v4 is stable.
2. Run the class-rename find-replace pass.
3. Update `data-toggle` → `data-bs-toggle` across all templates.
4. Replace `.dark-mode` toggle code with `data-bs-theme` writes.
5. Audit jQuery calls — remove them or import plugin classes.
6. Re-test responsive breakpoints (Bootstrap 5 adds `xxl`).

See the project's full [Migration from v3](https://adminlte.io/themes/v4/docs/migration.html) guide for the complete list.

---

## 12. Troubleshooting

### Error: the sidebar doesn't collapse when I click the hamburger

**Cause:** PushMenu isn't loaded, or the trigger uses the v3 attribute.
**Solution:** Confirm `adminlte.js` (or `adminlte.min.js`) is on the page and *after* the markup it operates on. Verify your hamburger button uses `data-lte-toggle="sidebar"` — **not** the v3 `data-widget="pushmenu"`.
**Prevention:** Keep the script tags at the end of `<body>` in the order shown in [Quick Start](#1-quick-start).

### Error: dark mode doesn't persist after refresh

**Cause:** Your toggle isn't writing to localStorage, or isn't reading it on load.
**Solution:** Use the [Color Mode toggle](#6-color-modes-light--dark--auto) snippet, which persists under the localStorage key `theme`. A custom toggle must both call `document.documentElement.setAttribute('data-bs-theme', …)` **and** write the choice to localStorage on change.

### Error: pressing Escape closes a Bootstrap modal *and* the sidebar

**Cause:** A pre-release (RC) build.
**Solution:** Update to the latest 4.0.0 — this was fixed (issue #5993). AdminLTE's accessibility module now defers Escape handling inside open modals to Bootstrap.

### Error: my custom SCSS doesn't override AdminLTE's defaults

**Cause:** Your overrides come *after* AdminLTE's `@import`.
**Solution:** Put your variable overrides **before** the import. AdminLTE variables use `!default`, so the first declaration wins (see [Section 7](#scss-variables-build-from-source)).

### Error: `npm install` fails with peer dependency errors

**Cause:** An npm older than 8.3, which ignores the `overrides` block in `package.json`.
**Solution:** Upgrade to npm 8.3+ (which respects `overrides`), or install with `--legacy-peer-deps` as a fallback.

### Error: Web Font CORS error loading FontAwesome `all.min.css`

**Cause:** Loading an icon font cross-origin without proper CORS headers (reported in issue #6008).
**Solution:** AdminLTE 4 standardizes on **Bootstrap Icons**, which is SVG-based and avoids the web-font CORS class of problems. If you bring your own FontAwesome, self-host it or use a CDN that serves the correct `Access-Control-Allow-Origin` headers for font files.

### Error: the fullscreen button shifts the icon's position

**Cause:** A pre-4.0.0 build that toggled inline `display` on the icon.
**Solution:** Update to 4.0.0 — the FullScreen plugin now toggles Bootstrap's `.d-none` utility instead (issue #6021), which works with any icon library.

### Error: content lands flush against the screen edge

**Cause:** Missing `.container-fluid` inside `.app-content`.
**Solution:** Wrap your content in `<div class="container-fluid">` (or `.container` for capped width).

---

## 13. FAQ

**Q: What is AdminLTE, exactly?**
A free, MIT-licensed admin dashboard *template* — HTML, CSS, and JavaScript built on Bootstrap 5.3. It is not a framework or an npm component library. You drop it into your project, edit the markup to suit your app, and ship.

**Q: Do I need a build step to use it?**
No. The CDN-only approach in [Quick Start](#1-quick-start) needs no install or build. The npm workflow is there if you want to customize SCSS variables or tree-shake the JavaScript.

**Q: Which Bootstrap version does v4 require?**
Bootstrap 5.3.x (the current `package.json` pins 5.3.8). Bootstrap 5.0/5.1/5.2 will **not** work — the color-mode and `data-bs-theme` system landed in 5.3.

**Q: Can I use AdminLTE 4 with jQuery?**
You don't need it — AdminLTE 4's JavaScript is vanilla. If your app already uses jQuery, the two coexist fine, but none of AdminLTE's plugins call into it and there are no `$.fn.xxx()` shims.

**Q: Can I use it with WordPress / Laravel / Symfony / Django / Rails?**
Yes — it's server-rendered HTML. Copy the demo pages into your views/templates, replace static content with your framework's template variables, and wire up routes and auth as usual. Community Composer packages exist for Laravel and Symfony, but they often lag upstream — verify they ship v4 before depending on them.

**Q: React / Vue / Svelte / Angular?**
Workable but less natural — AdminLTE's plugins use DOM lifecycle hooks that fight SPA reconciliation. The recommended approach is to use AdminLTE for its CSS and write your own framework components for the interactive parts, or pick a component library built for your framework.

**Q: How do I change the primary color?**
Override `--bs-primary` (and `--bs-primary-rgb`) on `:root` — no rebuild needed. See [Customizing the Look](#7-customizing-the-look).

**Q: Can I use FontAwesome instead of Bootstrap Icons?**
Yes. Nothing in AdminLTE requires Bootstrap Icons. Load FontAwesome's stylesheet and replace `<i class="bi bi-x">` with `<i class="fas fa-x">`.

**Q: Is AdminLTE free for commercial use?**
Yes — MIT licensed. Use it in commercial products, SaaS apps, client work, anything. The only requirement is preserving the copyright notice in the source files (`adminlte.css` / `adminlte.js`). No UI attribution is required.

**Q: Can I sell a product made with AdminLTE?**
Yes — the MIT license explicitly permits it, including reselling modified versions of AdminLTE itself, as long as you keep the original copyright notice.

**Q: How do I get notified of new versions?**
Watch the [GitHub repo](https://github.com/ColorlibHQ/AdminLTE) in Releases-only mode, or subscribe to the RSS feed at `https://github.com/ColorlibHQ/AdminLTE/releases.atom`.

---

## 14. Reference

### Plugin data-API attributes

| Attribute | Plugin | Goes on |
|---|---|---|
| `data-lte-toggle="sidebar"` | PushMenu | A button/link in the header |
| `data-lte-toggle="treeview"` | Treeview | The parent `<ul class="sidebar-menu">` |
| `data-lte-toggle="card-collapse"` | CardWidget | A button in `.card-tools` |
| `data-lte-toggle="card-remove"` | CardWidget | A button in `.card-tools` |
| `data-lte-toggle="card-maximize"` | CardWidget | A button in `.card-tools` |
| `data-lte-toggle="chat-pane"` | DirectChat | A button in a `.direct-chat` card |
| `data-lte-toggle="fullscreen"` | FullScreen | Any button |
| `data-lte-icon="maximize"` / `"minimize"` | FullScreen | Icons inside the fullscreen button |
| `data-lte-icon="expand"` / `"collapse"` | CardWidget | Icons inside the collapse button |

### Plugin config attributes

| Attribute | Default | Plugin | Goes on |
|---|---|---|---|
| `data-sidebar-breakpoint` | `992` | PushMenu | `.app-sidebar` |
| `data-enable-persistence` | `false` | PushMenu | `.app-sidebar` |
| `data-accordion` | `true` | Treeview | the parent `<ul>` |
| `data-animation-speed` | `300` | Treeview | the parent `<ul>` |

### CSS classes the plugins set

| Class | Set by | On | Meaning |
|---|---|---|---|
| `sidebar-collapse` | PushMenu | `<body>` | Sidebar collapsed |
| `sidebar-open` | PushMenu | `<body>` | Mobile sidebar explicitly open |
| `sidebar-mini` | PushMenu | `<body>` | Mini-sidebar mode |
| `menu-open` | Treeview | `.nav-item` | Submenu expanded |
| `collapsed-card` | CardWidget | `.card` | Card body collapsed |
| `maximized-card` | CardWidget | `<html>` + `.card` | Card maximized |
| `direct-chat-contacts-open` | DirectChat | `.direct-chat` | Contacts pane visible |
| `hold-transition` | Layout | `<body>` | Transitions briefly disabled |
| `app-loaded` | Layout | `<body>` | Initial load animation finished |
| `reduce-motion` | AccessibilityManager | `<body>` | OS `prefers-reduced-motion` detected |

### npm scripts (when building from source)

| Command | What it does |
|---|---|
| `npm start` / `npm run dev` | Dev server with file watching at `http://localhost:3000` |
| `npm run build` | Build all assets (clean + compile) |
| `npm run production` | Full production build with linting + bundlewatch |
| `npm run css` | Build CSS only (compile → prefix → RTL → minify) |
| `npm run js` | Build JavaScript only (compile → minify) |
| `npm run lint` | Run all linters (JS, CSS, docs, lockfile) |

### Runtime dependencies (loaded alongside AdminLTE)

| Dependency | Version (pinned) | Why |
|---|---|---|
| Bootstrap | 5.3.8 | The underlying component framework |
| Popper | 2.11.8 | Bootstrap dropdowns / tooltips / popovers |
| Bootstrap Icons | 1.13.1 | Default icon set |
| OverlayScrollbars | 2.11.0 | The sidebar's custom scrollbar |

### Browser support

AdminLTE 4 supports the same range as Bootstrap 5 — the latest stable releases of all major browsers. From the project's `.browserslistrc`: `Chrome >= 60`, `Firefox >= 60`, `iOS >= 12`, `Safari >= 12`, `not Explorer <= 11`.

---

_Generated June 2026 from ColorlibHQ/AdminLTE at 3c9e9f4. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
