# Technical Documentation: ColorlibHQ/AdminLTE

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/ColorlibHQ/AdminLTE)

### Overview (`README.md`)

# [AdminLTE — Bootstrap 5 Admin Dashboard](https://adminlte.io)

[](https://www.npmjs.com/package/admin-lte)
[](https://packagist.org/packages/almasaeed2010/adminlte)
[](https://www.jsdelivr.com/package/npm/admin-lte)
[](https://opensource.org/licenses/MIT)
[](https://discord.gg/jfdvjwFqfz)
[](https://app.netlify.com/sites/adminlte-v4/deploys)

**AdminLTE** is the most popular open-source admin dashboard template — fully responsive,
built on **[Bootstrap 5.3](https://getbootstrap.com/)** with vanilla JavaScript (no jQuery),
highly customizable, and easy to use. It fits every screen from small mobile devices to
large desktops, and it's MIT-licensed.

**[Live Demo](https://adminlte.io/themes/v4/)** ·
**[Documentation](https://adminlte.io/themes/v4/docs/introduction.html)** ·
**[Framework Editions](#framework-editions)** ·
**[Premium Templates](#premium-templates)**

<p align="center">
  <a href="https://adminlte.io/themes/v4/">
    <img alt="AdminLTE 4 dashboard — light mode" src=".github/assets/dashboard-light.webp" width="49%">
  </a>
  <a href="https://adminlte.io/themes/v4/">
    <img alt="AdminLTE 4 dashboard — dark mode" src=".github/assets/dashboard-dark.webp" width="49%">
  </a>
</p>

## Framework editions

The same AdminLTE 4 dashboard, officially integrated for the framework you know best —
you're looking at the **HTML / Bootstrap** core:


<div align="center">
  <a href="https://github.com/ColorlibHQ/AdminLTE"><img height="36" alt="HTML" src="https://img.shields.io/badge/HTML-0D6EFD?style=for-the-badge&logo=html5&logoColor=white"></a>
  <a href="https://github.com/ColorlibHQ/adminlte-react"><img height="36" alt="React" src="https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB"></a>
  <a href="https://github.com/ColorlibHQ/adminlte-react"><img height="36" alt="Next.js" src="https://img.shields.io/badge/Next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white"></a>
  <a href="https://github.com/ColorlibHQ/adminlte-vue"><img height="36" alt="Vue" src="https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs&logoColor=4FC08D"></a>
  <a href="https://github.com/ColorlibHQ/adminlte-vue"><img height="36" alt="Nuxt" src="https://img.shields.io/badge/Nuxt-00DC82?style=for-the-badge&logo=nuxt&logoColor=white"></a>
  <a href="https://github.com/ColorlibHQ/adminlte-angular"><img height="36" alt="Angular" src="https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white"></a>
  <a href="https://github.com/ColorlibHQ/adminlte-laravel"><img height="36" alt="Laravel" src="https://img.shields.io/badge/Laravel-FF2D20?style=for-the-badge&logo=laravel&logoColor=white"></a>
  <a href="https://github.com/ColorlibHQ/adminlte-symfony"><img height="36" alt="Symfony" src="https://img.shields.io/badge/Symfony-000000?style=for-the-badge&logo=symfony&logoColor=white"></a>
  <a href="https://github.com/ColorlibHQ/adminlte-django"><img height="36" alt="Django" src="https://img.shields.io/badge/Django-092E20?style=for-the-badge&logo=django&logoColor=white"></a>
  <a href="https://github.com/ColorlibHQ/adminlte-aspnet"><img height="36" alt="ASP.NET" src="https://img.shields.io/badge/ASP.NET-512BD4?style=for-the-badge&logo=dotnet&logoColor=white"></a>
  <a href="https://github.com/ColorlibHQ/adminlte-drupal"><img height="36" alt="Drupal" src="https://img.shields.io/badge/Drupal-0678BE?style=for-the-badge&logo=drupal&logoColor=white"></a>
  <a href="https://docs.adminlte.io"><img height="36" alt="Docs" src="https://img.shields.io/badge/Docs-adminlte.io-0EA5E9?style=for-the-badge&logo=readthedocs&logoColor=white"></a>
</div>


| Edition | Repository | Live demo | Install |
|---|---|---|---|
| **HTML / Bootstrap** (this repo) | [AdminLTE](https://github.com/ColorlibHQ/AdminLTE) | [themes/v4](https://adminlte.io/themes/v4/) | `npm install admin-lte` |
| **React & Next.js** — 30+ typed components, RSC-ready, ⌘K palette | [adminlte-react](https://github.com/ColorlibHQ/adminlte-react) | [themes/next-react](https://adminlte.io/themes/next-react/) | see repo |
| **Vue 3 & Nuxt** — 45+ typed components, composables, SSR-safe theming | [adminlte-vue](https://github.com/ColorlibHQ/adminlte-vue) | [themes/vue-nuxt](https://adminlte.io/themes/vue-nuxt/) | see repo |
| **Laravel** — Blade components, config-driven menu, auth scaffolding | [adminlte-laravel](https://github.com/ColorlibHQ/adminlte-laravel) | [laravel.adminlte.io](https://laravel.adminlte.io/) | `composer require colorlibhq/adminlte-laravel` |
| **Django** — reusable app, menu filter pipeline, themed admin | [adminlte-django](https://github.com/ColorlibHQ/adminlte-django) | [django.adminlte.io](https://django.adminlte.io/) | `pip install django-adminlte4` |
| **Symfony** — Twig Components, AssetMapper, config-driven menu, EasyAdmin theme | [adminlte-symfony](https://github.com/ColorlibHQ/adminlte-symfony) | see repo | `composer require colorlibhq/adminlte-symfony` |
| **Angular 22** — 44 standalone signal components, dark mode, ⌘K palette | [adminlte-angular](https://github.com/ColorlibHQ/adminlte-angular) | see repo | `npm i @adminlte/angular` |
| **ASP.NET Core (.NET 10)** — Blazor components + MVC/Razor Pages Tag Helpers | [adminlte-aspnet](https://github.com/ColorlibHQ/adminlte-aspnet) | see repo | `dotnet add package ColorlibHQ.AdminLTE.AspNetCore` |
| **Drupal** — admin theme for Drupal 10.3+/11, themed admin UI | [adminlte-drupal](https://github.com/ColorlibHQ/adminlte-drupal) | see repo | see repo |
| **Docs** — guides, components, and API reference for every edition | [docs.adminlte.io](https://docs.adminlte.io) | [docs.adminlte.io](https://docs.adminlte.io) | — |

Every edition ships the full AdminLTE 4 design — Bootstrap 5.3, dark mode, RTL — with
idiomatic integrations for its stack (components, routing, auth, theming).

## Quick start

**CDN** — no build step:

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@4/dist/css/adminlte.min.css">
<script src="https://cdn.jsdelivr.net/npm/admin-lte@4/dist/js/adminlte.min.js"></script>
```

**npm:**

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

**Composer:**

```bash
composer require almasaeed2010/adminlte
```

Then start from the [Getting Started guide](https://adminlte.io/themes/v4/docs/introduction.html)
or copy one of the demo pages.

### Developing AdminLTE itself

1. **Install dependencies:** use Node.js 22.22.3+, 24.16.0+, or 26.3.0+ and npm 10+, then run `npm ci`
2. **Start the dev server:** `npm start` *(opens http://localhost:3000 with live reload)*
3. **Build:** `npm run build` — or `npm run production` for the full lint + test + optimize + bundle-size pipeline

<details>
<summary>All npm scripts</summary>

- `npm start` — development server with file watching
- `npm run build` — build all assets for development
- `npm run production` — full production build with linting, unit tests, and local bundle-size budgets
- `npm run lint` — run all linters (JS, CSS, docs, lockfile)
- `npm test` — unit tests (Vitest)
- `npm run test-browser` — built-page, responsive, theme, and plugin checks (install Chromium with `npx playwright install chromium`)
- `npm run test-a11y` — axe accessibility checks on built pages (also test dark mode with `A11Y_COLOR_SCHEME=dark`)
- `npm run test-size` — check gzip budgets without external reporting services
- `npm run css` — build CSS only
- `npm run js` — build JavaScript only

</details>

## What's new in v4

The v4 line is a ground-up rewrite on Bootstrap 5.3 with **no jQuery**: 18 new demo pages
(Calendar, Kanban, Chat, File Manager, Mailbox, Wizard, Tabulator data tables, and more),
a documentation overhaul, and major dependency upgrades. See the
[CHANGELOG](CHANGELOG.md) for full details.

### New in 4.3

- **Sidebar search** — a new `SidebarSearch` plugin filters the menu as you type, expanding
  whatever submenu holds a match and restoring the menu exactly as it was when cleared.
  Paired with a navbar search in the app header.
- **Ribbons** — corner banners (`.ribbon-wrapper` + `.ribbon`) in three sizes, mirrored
  automatically in RTL, with the clip following the card's corner radius.
- **Social & post widgets** — `.user-block`, `.post`, `.widget-user`, `.widget-user-2` and
  `.description-block`, laid out with grid and flex.
- **Three new demo pages** — Gallery (filterable media library, no image library required),
  Search Results (typed results with a refine panel), and Ribbons.
- **Nav tabs, modals and offcanvas** added to the General UI Elements showcase.
- **Card and Miscellaneous docs pages**, plus a language switcher recipe — every component
  that ships CSS now has a reference page.

<details>
<summary>Highlights</summary>

**18 new demo pages**

- Apps: Calendar (FullCalendar), Kanban (SortableJS), Chat, File Manager, Projects, Mailbox (Inbox / Read / Compose)
- Forms: Wizard (4-step with validation)
- Tables: Data Tables (Tabulator — jQuery-free)
- Pages: Profile, Settings, Invoice, Pricing, FAQ
- Errors: 404, 500, Maintenance

**Documentation overhaul**

- New pages: Getting Started, Customization & Theming, RTL Support, Migration from v3, Layout Blueprint, Recipes, Deployment & Performance, Recommended Integrations, JavaScript Plugins Overview
- Rewritten Introduction with four labelled install paths (CDN / npm / source / Composer)
- FAQ rebuilt with hero, live search, section chips, and an accordion of 23 questions
- Split sidebar navigation: dashboard demo and docs each have their own nav

**Major dependency upgrades**

- ESLint 10, TypeScript 6, Stylelint 17, Astro 6.3, Bootstrap 5.3.8, Node 22 LTS in CI
- `npm install` runs clean with **0 vulnerabilities**

</details>

<details>
<summary>Breaking changes from v3</summary>

- Class renames: `.wrapper` → `.app-wrapper`, `.main-header` → `.app-header`, `.main-sidebar` → `.app-sidebar`, `.content-wrapper` → `.app-main`
- Data attributes: `data-toggle` → `data-bs-toggle`, `data-widget="pushmenu"` → `data-lte-toggle="sidebar"`, `data-widget="treeview"` → `data-lte-toggle="treeview"`
- Dark mode: `.dark-mode` body class → `data-bs-theme="dark"` attribute (Bootstrap 5.3 native)
- jQuery no longer required; plugins are vanilla TypeScript
- The bundled `plugins/` folder is gone — every v3 jQuery widget has a documented vanilla-JS successor ([replacement table](https://adminlte.io/themes/v4/docs/migration.html#third-party-plugin-replacements): Select2 → Tom Select, DataTables → Tabulator, Summernote → Quill, …)
- The v3 extra colours (`.bg-navy`, `.bg-teal`, the sidebar skins, …) live in opt-in stylesheets: `dist/css/adminlte-colors.css` — fourteen redesigned colours, every one readable with white text, 17 skin presets, plus a one-line hook for your brand colour — or `dist/css/adminlte-colors-v3.css`, the 18 AdminLTE 3 colours exactly as they were. Either sheet also lets you make one of them Bootstrap's `primary` — `<html data-lte-primary="teal">` recolours buttons, links, pagination and form focus ([Colors docs](https://adminlte.io/themes/v4/docs/colors.html), [live demo](https://adminlte.io/themes/v4/UI/colors.html)). Nothing is added to `adminlte.css`.

See the dedicated [Migration from v3](https://adminlte.io/themes/v4/docs/migration.html) guide.

</details>

## Premium templates

AdminLTE will always be free and open source. When a project needs more —
app-ready pages, framework-native codebases, dedicated support — our team
hand-picks premium dashboards at **[adminlte.io/premium](https://adminlte.io/premium)**,
including editions built for the same stacks AdminLTE integrates with:

<table>
  <tr>
    <td align="center" width="50%">
      <a href="https://dashboardpack.com/theme-details/admindek-html/?utm_source=github&utm_medium=readme&utm_campaign=adminlte">
        <img src=".github/assets/premium/admindek.webp" alt="Admindek — feature-rich Bootstrap 5 dashboard with dark mode" width="100%">
      </a>
      <br>
      <a href="https://dashboardpack.com/theme-details/admindek-html/?utm_source=github&utm_medium=readme&utm_campaign=adminlte"><strong>Admindek</strong></a>
      <br>
      <sub>The natural next step from AdminLTE: Bootstrap 5 + vanilla JS, 100+ components, dark/light modes, RTL, 10 color presets.<br>
      Also for <a href="https://dashboardpack.com/theme-details/admindek-dashboard-laravel/?utm_source=github&utm_medium=readme&utm_campaign=adminlte">Laravel</a> ·
      <a href="https://dashboardpack.com/theme-details/admindek-nextjs/?utm_source=github&utm_medium=readme&utm_campaign=adminlte">Next.js</a> ·
      <a href="https://dashboardpack.com/theme-details/admindek-dashboard-angular/?utm_source=github&utm_medium=readme&utm_campaign=adminlte">Angular</a></sub>
    </td>
    <td align="center" width="50%">
      <a href="https://dashboardpack.com/theme-details/apex-dashboard-nextjs/?utm_source=github&utm_medium=readme&utm_campaign=adminlte">
        <img src=".github/assets/premium/apex.webp" alt="Apex Dashboard — admin template available for Next.js, Laravel, Django and Angular" width="100%">
      </a>
      <br>
      <a href="https://dashboardpack.com/theme-details/apex-dashboard-nextjs/?utm_source=github&utm_medium=readme&utm_campaign=adminlte"><strong>Apex Dashboard</strong></a>
      <br>
      <sub>5 dashboard variants, 20+ app pages, 125+ routes, full CRUD — in your backend's native stack.<br>
      For <a href="https://dashboardpack.com/theme-details/apex-dashboard-nextjs/?utm_source=github&utm_medium=readme&utm_campaign=adminlte">Next.js</a> ·
      <a href="https://dashboardpack.com/theme-details/apex-dashboard-laravel/?utm_source=github&utm_medium=readme&utm_campaign=adminlte">Laravel</a> ·
      <a href="https://dashboardpack.com/theme-details/apex-dashboard-django/?utm_source=github&utm_medium=readme&utm_campaign=adminlte">Django</a> ·
      <a href="https://dashboardpack.com/theme-details/apex-dashboard-angular/?utm_source=github&utm_medium=readme&utm_campaign=adminlte">Angular</a></sub>
    </td>
  </tr>
  <tr>
    <td align="center" width="50%">
      <a href="https://dashboardpack.com/theme-details/zenith-dashboard-django/?utm_source=github&utm_medium=readme&utm_campaign=adminlte">
        <img src=".github/assets/premium/zenith.webp" alt="Zenith — ultra-minimal admin dashboard, Django edition" width="100%">
      </a>
      <br>
      <a href="https://dashboardpack.com/theme-details/zenith-dashboard-django/?utm_source=github&utm_medium=readme&utm_campaign=adminlte"><strong>Zenith Dashboard — Django</strong></a>
      <br>
      <sub>Achromatic, ultra-minimal design as a ready-to-run Django project: 50+ pages, 6 dashboards, live theme customizer.</sub>
    </td>
    <td align="center" width="50%">
      <a href="https://dashboardpack.com/theme-details/haze-dashboard-nuxt/?utm_source=github&utm_medium=readme&utm_campaign=adminlte">
        <img src=".github/assets/premium/haze.webp" alt="Haze — Nuxt 4 admin dashboard with 92+ pages and 5 dashboards" width="100%">
      </a>
      <br>
      <a href="https://dashboardpack.com/theme-details/haze-dashboard-nuxt/?utm_source=github&utm_medium=readme&utm_campaign=adminlte"><strong>Haze — Nuxt</strong></a>
      <br>
      <sub>Nuxt 4 + Nuxt UI v4 + Tailwind CSS v4. 92+ pages, 7 layouts, 5 dashboards, RTL, i18n, mock API layer.</sub>
    </td>
  </tr>
</table>

<p align="center">
  <a href="https://adminlte.io/premium"><strong>View all premium templates →</strong></a>
</p>

## Browser & platform support

AdminLTE supports the latest versions of all modern browsers (Chrome, Firefox, Safari,
Edge) via Bootstrap 5.3.8. The build scripts run cross-platform — Windows (CMD,
PowerShell, Git Bash), macOS and Linux — using cross-platform npm utilities throughout.

## Security & production deployment

AdminLTE is a **UI template**. Deploy only the compiled production assets
(`dist/js/adminlte.min.js`, `dist/css/adminlte.min.css`) and your own application files —
never `node_modules/`, the demo HTML pages, or the `src/` directory.

> **About CVE-2021-36471:** this CVE is **disputed** and does not represent a
> vulnerability in AdminLTE — it refers to demo pages being accessible when example
> files are incorrectly deployed to production. AdminLTE v4 cleanly separates
> development demos from production assets.

For detailed guidelines, authentication requirements, and best practices, see
[SECURITY.md](SECURITY.md).

## Sponsorship

Support AdminLTE development by becoming a sponsor or donor.

<p align="center">
  <a href="https://github.com/sponsors/danny007in">
    <img src="https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86" alt="Sponsor on GitHub" />
  </a>
  &nbsp;&nbsp;
  <a href="https://www.paypal.me/daniel007in">
    <img src="https://img.shields.io/static/v1?label=Donate&message=%E2%9D%A4&logo=PayPal&color=%2300457C" alt="Donate via PayPal" />
  </a>
</p>

### Our sponsors

<p align="center">
  <a href="https://github.com/spizzo14"><img src="https://unavatar.io/github/spizzo14?fallback=https%3A%2F%2Fraw.githubusercontent.com%2FJamesIves%2Fgithub-sponsors-readme-action%2Fdev%2F.github%2Fassets%2Fplaceholder.png" width="50" height="50" alt="User avatar: spizzo14" loading="lazy" /></a>&nbsp;&nbsp;
  <a href="https://github.com/tomhappyblock"><img src="https://unavatar.io/github/tomhappyblock?fallback=https%3A%2F%2Fraw.githubusercontent.com%2FJamesIves%2Fgithub-sponsors-readme-action%2Fdev%2F.github%2Fassets%2Fplaceholder.png" width="50" height="50" alt="User avatar: tomhappyblock" loading="lazy" /></a>&nbsp;&nbsp;
  <a href="https://github.com/stefanmorderca"><img src="https://unavatar.io/github/stefanmorderca?fallback=https%3A%2F%2Fraw.githubusercontent.com%2FJamesIves%2Fgithub-sponsors-readme-action%2Fdev%2F.github%2Fassets%2Fplaceholder.png" width="50" height="50" alt="User avatar: stefanmorderca" loading="lazy" /></a>&nbsp;&nbsp;
  <a href="https://github.com/tito10047"><img src="https://unavatar.io/github/tito10047?fallback=https%3A%2F%2Fraw.githubusercontent.com%2FJamesIves%2Fgithub-sponsors-readme-action%2Fdev%2F.github%2Fassets%2Fplaceholder.png" width="50" height="50" alt="User avatar: tito10047" loading="lazy" /></a>&nbsp;&nbsp;
  <a href="https://github.com/sitchi"><img src="https://unavatar.io/github/sitchi?fallback=https%3A%2F%2Fraw.githubusercontent.com%2FJamesIves%2Fgithub-sponsors-readme-action%2Fdev%2F.github%2Fassets%2Fplaceholder.png" width="50" height="50" alt="User avatar: sitchi" loading="lazy" /></a>&nbsp;&nbsp;
  <a href="https://github.com/npreee"><img src="https://unavatar.io/github/npreee?fallback=https%3A%2F%2Fraw.githubusercontent.com%2FJamesIves%2Fgithub-sponsors-readme-action%2Fdev%2F.github%2Fassets%2Fplaceholder.png" width="50" height="50" alt="User avatar: npreee" loading="lazy" /></a>&nbsp;&nbsp;
  <a href="https://github.com/isaacmorais"><img src="https://unavatar.io/github/isaacmorais?fallback=https%3A%2F%2Fraw.githubusercontent.com%2FJamesIves%2Fgithub-sponsors-readme-action%2Fdev%2F.github%2Fassets%2Fplaceholder.png" width="50" height="50" alt="User avatar: isaacmorais" loading="lazy" /></a>&nbsp;&nbsp;
</p>

<p align="center">
  <a href="https://github.com/sponsors/danny007in">Your avatar here? Become a sponsor</a>
</p>

## Contributing

Contributions are highly welcome:

1. Install [Node.js](https://nodejs.org/) (LTS) and clone this repository (`master` branch).
2. `npm install`, then `npm start` to run the dev server.
3. Make your changes (run `npm run lint` before committing) and open a PR against `master`.

## License

AdminLTE is an open source project by [AdminLTE.io](https://adminlte.io) licensed under
[MIT](https://opensource.org/licenses/MIT). AdminLTE.io reserves the right to change
the license of future releases.

The charts on the demo pages are drawn with [Chart.js](https://www.chartjs.org/) (MIT),
loaded from a CDN on the pages that use it — it is not a dependency of the `admin-lte` package.

## Image credits

[Pixeden](http://www.pixeden.com/psd-web-elements/flat-responsive-showcase-psd),
[Graphicsfuel](https://www.graphicsfuel.com/2013/02/13-high-resolution-blur-backgrounds/),
[Pickaface](https://pickaface.net/),
[Unsplash](https://unsplash.com/),
[Uifaces](http://uifaces.com/),
[Unavatar](https://unavatar.io/)

---

### Src Html Components Docs Getting Started (`src/html/components/docs/getting-started.mdx`)

AdminLTE is an HTML template — there's no build step required to use it. This page gets you to a working dashboard in about ten minutes, without npm, without a build pipeline, without learning Astro.

If you're integrating AdminLTE into a real project, the [Installation](introduction.html) page covers npm, Composer, and source-build workflows.

##### 1. Grab the CDN snippets

Copy these tags into the `<head>` of a new HTML file. They pull AdminLTE, Bootstrap 5.3, Popper, OverlayScrollbars, and Bootstrap Icons from jsDelivr — no install needed.

```html

<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.16.0/styles/overlayscrollbars.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@4.10.1/dist/css/adminlte.min.css" />
```

And these just before `</body>`:

```html

<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.16.0/browser/overlayscrollbars.browser.es6.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/admin-lte@4.10.1/dist/js/adminlte.min.js"></script>
```

##### 2. The minimum viable layout

This is the smallest AdminLTE page that still feels like AdminLTE — a header, a collapsible sidebar, and a main content area. Drop it between the CSS and JS tags above:

```
/* Detailed source-code truncated for AI context efficiency. */
```

Open it in a browser. The hamburger button at the top-left collapses the sidebar — that's AdminLTE's [PushMenu](javascript/pushmenu.html) plugin working out of the box, wired by `data-lte-toggle="sidebar"`.

##### 3. What's actually happening

The four classes that do the heavy lifting:

| Class | What it does |
|---|---|
| `app-wrapper` | The CSS grid root — defines header / sidebar / main / footer regions |
| `app-header` | Stuck to the top of the viewport when `layout-fixed` is on the body |
| `app-sidebar` | Collapsible side rail. Add `sidebar-expand-lg` to body to control breakpoint |
| `app-main` | Scrollable content region — everything else lives here |

The [Layout](layout.html) page covers the full wrapper structure and the responsive options.

##### 4. Add a working JavaScript widget

AdminLTE ships seven JavaScript plugins. All of them are wired via `data-lte-*` attributes — no JavaScript code needed.

Drop this in the content area to see the card-widget plugin in action (try the buttons in the card header):

```html
<div class="card">
  <div class="card-header">
    <h5 class="card-title">Collapsible card</h5>
    <div class="card-tools">
      <button type="button" class="btn btn-sm btn-tool" data-lte-toggle="card-collapse" aria-label="Collapse card">
        <i data-lte-icon="expand" class="bi bi-dash-lg"></i>
        <i data-lte-icon="collapse" class="bi bi-plus-lg"></i>
      </button>
      <button type="button" class="btn btn-sm btn-tool" data-lte-toggle="card-remove" aria-label="Remove card">
        <i class="bi bi-x-lg"></i>
      </button>
    </div>
  </div>
  <div class="card-body">
    Click the minus icon to collapse; the X to remove.
  </div>
</div>
```

##### 5. Where to go next

You now have a working AdminLTE page. From here:

- **Make it pretty** → [Customization & Theming](customization.html) — change colors, fonts, and spacing via SCSS variables or CSS custom properties.
- **Make it functional** → [JavaScript Plugins](javascript/layout.html) — full reference for all seven plugins.
- **Make it dark** → [Color Mode](color-mode.html) — light/dark/auto toggle.
- **Make it international** → [RTL Support](rtl.html) — right-to-left layouts for Arabic, Hebrew, Persian, etc.
- **Add real widgets** → [Recommended Integrations](integrations.html) — date pickers, multi-selects, charts, rich text editors. AdminLTE keeps its dep tree lean; this page tells you which third-party libraries to drop in.

If you're upgrading an existing AdminLTE 3 project, see the [Migration from v3](migration.html) guide for breaking changes.

---

### Src Html Components Docs Browser Support (`src/html/components/docs/browser-support.mdx`)

AdminLTE comes with the same browser support as Bootstrap 5.

> ##### Excerpt from Bootstrap's official Documentation!
>
> Bootstrap supports the **latest, stable releases** of all major browsers and platforms.
>
> For more details [look here](https://getbootstrap.com/docs/5.0/getting-started/browsers-devices/#supported-browsers).

You can find our supported range of browsers and their versions in [our .browserslistrc file](https://github.com/ColorlibHQ/AdminLTE/blob/master/.browserslistrc):

```
# https://github.com/browserslist/browserslist#readme

>= 0.5%
last 2 major versions
not dead
Chrome >= 60
Firefox >= 60
Firefox ESR
iOS >= 12
Safari >= 12
not Explorer <= 11
```

---

### Src Html Components Docs Color Mode (`src/html/components/docs/color-mode.mdx`)

AdminLTE supports Bootstrap 5.3 color modes (light, dark, and auto) out of the box. Since 4.1 the switcher ships in `adminlte.js` as the **ColorMode** module — you no longer need to copy a script into your pages.

##### How it works

- The chosen mode is persisted in `localStorage` under the key `lte-theme`.
- `auto` follows the operating system's `prefers-color-scheme` and updates live when the OS preference changes.
- The resolved mode is applied as `data-bs-theme="light|dark"` on `<html>`, so every Bootstrap and AdminLTE component adapts automatically.

The mode is resolved from three sources, in this order:

1. **The visitor's stored choice** — `localStorage.lte-theme`, written when they click a toggle. It is their own click on this device, so it wins.
2. **The theme your page declared** — `data-bs-theme` on `<html>` as it was served. Use this to render a per-user preference from a cookie or a user record; it survives OS preference changes.
3. **The OS preference** — `prefers-color-scheme`, followed live while neither of the above expressed a choice.

Only `light`, `dark` and `auto` are understood. A custom Bootstrap theme name in `data-bs-theme` is not a mode ColorMode can resolve, so it is ignored — see *Opting out* below.

##### Markup

Add toggle buttons anywhere with `data-bs-theme-value` — the module binds them automatically. Optional `data-lte-theme-icon` elements act as an indicator for the current choice (the demo uses them in the topbar trigger):

```html
<li class="nav-item dropdown">
  <a
    class="nav-link"
    href="#"
    id="bd-theme"
    aria-label="Toggle color scheme"
    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>
  </a>
  <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="bd-theme">
    <li>
      <button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light" aria-pressed="false">
        <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" aria-pressed="false">
        <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 active" data-bs-theme-value="auto" aria-pressed="true">
        <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 module keeps the `active` class, `aria-pressed` state, and the `.bi-check-lg` check marks in sync on every `[data-bs-theme-value]` button.

##### JavaScript API

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

const colorMode = new ColorMode()

colorMode.getPreferredTheme() // "light" | "dark" | "auto" — the effective choice
colorMode.getStoredTheme()    // what the visitor picked, or null
colorMode.getMarkupTheme()    // what the page declared on <html>, or null
colorMode.setTheme("dark")    // apply + persist + sync toggles

// React to changes (fired on document)
document.addEventListener("changed.lte.color-mode", (event) => {
  console.log(event.detail.theme)    // what the user chose: "light" | "dark" | "auto"
  console.log(event.detail.resolved) // what got applied:    "light" | "dark"
})
```

##### Preventing the flash of wrong theme

The module runs after the DOM is ready, so pages should also apply the stored theme *before first paint* with a tiny inline snippet in `<head>` (this is the one piece that must stay inline — see `#6043`):

```html
<script>
  (() => {
    "use strict"
    const root = document.documentElement
    if (root.getAttribute("data-lte-color-mode") === "off") return
    let stored = null
    try { stored = localStorage.getItem("lte-theme") } catch {}
    const authored = root.getAttribute("data-bs-theme")
    let resolved = "light"
    if (stored === "dark" || stored === "light") resolved = stored
    else if (authored === "dark" || authored === "light") resolved = authored
    else if (matchMedia("(prefers-color-scheme: dark)").matches) resolved = "dark"
    root.setAttribute("data-bs-theme", resolved)
    root.style.colorScheme = resolved
    if (resolved !== authored) root.setAttribute("data-lte-theme-resolved", "")
  })()
</script>
```

It follows the same precedence as the module, so a theme you rendered server-side is not overwritten before paint. The last line matters: it flags a value the snippet worked out itself, so the module doesn't mistake it for a theme your page declared and stop following the OS preference.

##### Server-rendered themes

If you store a per-user preference server-side, render it on `<html>` and the module will keep it — on load, and across OS preference changes:

```html
<html lang="en" data-bs-theme="dark">
```

It remains a default: the moment a visitor clicks a toggle, their choice is persisted and takes precedence on that device from then on. Clear `lte-theme` from `localStorage` when you want the server's value to take over again.

##### Opting out

Applications with their own theming — a custom Bootstrap theme, or a switcher of your own — turn the module off entirely:

```html
<html lang="en" data-lte-color-mode="off">
```

ColorMode then never writes `data-bs-theme`: not on load, not when a `[data-bs-theme-value]` toggle is clicked, and not when the OS preference changes. The inline snippet above honours it too, so nothing touches your theme before first paint either. Your own code stays in full control of `<html data-bs-theme>`.

##### Per-region color modes

`data-bs-theme` also works on any element, not just `<html>` — for example a permanently dark sidebar in an otherwise light layout:

```html
<aside class="app-sidebar" data-bs-theme="dark">...</aside>
```

---

### Src Html Components Docs Colors (`src/html/components/docs/colors.mdx`)

import PaletteWheel from "./_palette-wheel.astro"
import SkinPreview from "../_skin-preview.astro"
import { palette, paletteV3, sets, skinGroups, skinGroupsV3, skins, skinsV3 } from "../../../utils/palette.mjs"

AdminLTE 4 styles itself with Bootstrap 5.3's theme colours — `primary`, `success`, `danger`, `warning`, `info`, `secondary`, `light`, `dark` — and nothing else. That keeps `adminlte.css` small, but a dashboard often wants more than five hues on a widget row, and AdminLTE 3 users miss `.bg-navy`, `.bg-teal` and the sidebar skins.

**`dist/css/adminlte-colors.css`** is the answer, and it is opt-in: one extra stylesheet (≈ 8 kB gzipped) that adds fourteen colours as `--bs-*` tokens plus every class family Bootstrap generates for its own theme colours — and lets any of them [become your `primary`](#making-one-of-them-your-primary). Nothing changes for projects that don't load it. Prefer the AdminLTE 3 colours exactly as they were? There is a second sheet for that — see [Two palettes](#two-palettes) below.

<div class="d-flex flex-wrap gap-2 my-3">
  {palette.map(c => (
    <span class={`badge text-bg-${c.name} fs-6 fw-normal`}>{c.name}</span>
  ))}
</div>

Try every colour, skin and widget set live on the <a href="../UI/colors.html">Colors demo page</a> — it also has a checker for your own brand colours.

##### Getting the stylesheet

Load it **after** `adminlte.css`. It only adds classes; nothing in `adminlte.css` is overridden.

```html
<link rel="stylesheet" href="css/adminlte.css" />
<link rel="stylesheet" href="css/adminlte-colors.css" />
```

From the CDN (pin the exact version you tested against in production):

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@4.10.1/dist/css/adminlte-colors.min.css" />
```

From npm, in a bundler:

```js
import "admin-lte/dist/css/adminlte.min.css"
import "admin-lte/dist/css/adminlte-colors.min.css"
```

Or compile it yourself — the Sass entry is `src/scss/adminlte-colors.scss`, and that is also how you add your own colours (below). An RTL build (`adminlte-colors.rtl.css`) is generated for symmetry with the rest of the CSS; the sheet has no direction-dependent rules, so either file works in either direction.

##### Two palettes

You can have the colours **designed for 4.4** (this page's default, below) or the **AdminLTE 3 originals**, unchanged. They ship as two files that define the same class names with different values, so load **one** of them after `adminlte.css`:

| File | What you get | Text colour rule |
|---|---|---|
| `dist/css/adminlte-colors.css` | 14 colours designed in OKLCH — orange, amber, olive, teal, sky, indigo, violet, fuchsia, pink, navy, steel, slate, graphite, midnight — every one readable with white text, placed to sit next to Bootstrap's theme colours | Bootstrap 5's `color-contrast()`, 4.5:1 |
| `dist/css/adminlte-colors-v3.css` | the 18 AdminLTE 3 colours — lightblue, navy, olive, lime, fuchsia, maroon, blue, indigo, purple, pink, red, orange, yellow, green, teal, cyan, gray, gray-dark — values as they were in the 3.2 build | AdminLTE 3's rule (Bootstrap 4 YIQ): white on lightblue, olive, teal, cyan …; dark on lime, orange, yellow. Eight miss WCAG AA — [`data-lte-contrast="aa"`](#making-the-v3-colours-pass-aa) flips those |

Both emit exactly the same class families (`.bg-*`, `.text-bg-*`, `.text-*`, `.border-*`, `.link-*`, `.bg-gradient-*`, `.card-*`, `.callout-*`, `.direct-chat-*`, tokens with dark-mode variants), so everything on this page applies to either. The demo pages let you switch between them live: the <a href="../UI/colors.html">Colors demo</a> and <a href="../generate/theme.html">Theme Customize</a> both have a palette toggle.

```html

<link rel="stylesheet" href="css/adminlte-colors.css" />

<link rel="stylesheet" href="css/adminlte-colors-v3.css" />
```

The AdminLTE 3 originals, with the contrast ratio white text reaches on each — several are below WCAG AA's 4.5:1 for normal text, exactly as they were in v3, which is the trade-off of keeping them as they were:

<div class="table-responsive">
  <table class="table align-middle">
    <thead>
      <tr>
        <th scope="col">Name</th>
        <th scope="col">Hex</th>
        <th scope="col">Text (v3 rule)</th>
        <th scope="col">White text</th>
        <th scope="col"></th>
      </tr>
    </thead>
    <tbody>
      {paletteV3.map(c => (
        <tr>
          <td class="text-nowrap">
            <span class="badge me-2" style={`background:${c.hex}`}>&nbsp;&nbsp;&nbsp;</span>
            <code>{c.name}</code>
          </td>
          <td><code>{c.hex}</code></td>
          <td>{c.textColor === "white" ? "white" : "dark (#1f2d3d)"}</td>
          <td class="text-nowrap">{c.contrastWhite.toFixed(1)}:1{c.textColor === "white" && c.contrastWhite < 4.5 ? " ⚠︎" : ""}</td>
          <td class="text-body-secondary">{c.note}</td>
        </tr>
      ))}
    </tbody>
  </table>
</div>

##### Making the v3 colours pass AA

**`data-lte-contrast="aa"` fixes the eight without touching a single value.** The v3 rule assigns white text to eight colours that do not reach WCAG AA's 4.5:1 for normal text. The attribute flips just those to the ink that does, leaving every background byte-identical:

```html
<html lang="en" data-lte-contrast="aa">
```

<div class="table-responsive">
  <table class="table align-middle">
    <thead>
      <tr>
        <th scope="col">Colour</th>
        <th scope="col">Default (v3 rule)</th>
        <th scope="col">With <code>aa</code></th>
      </tr>
    </thead>
    <tbody>
      {paletteV3.filter(c => c.textColor !== c.textColorAA).map(c => (
        <tr>
          <td class="text-nowrap"><span class="badge me-2" style={`background:${c.hex}`}>&nbsp;&nbsp;&nbsp;</span><code>{c.name}</code> <span class="text-body-secondary">{c.hex}</span></td>
          <td class="text-nowrap">white text — <span class="text-danger-emphasis fw-semibold">{c.contrastWhite.toFixed(2)}:1</span></td>
          <td class="text-nowrap">black text — <span class="text-success-emphasis fw-semibold">{c.contrastBlack.toFixed(2)}:1</span></td>
        </tr>
      ))}
    </tbody>
  </table>
</div>

The ratios are stated rather than shown, because rendering the failing combination would put text on this page that fails the same check — flip the switch on the <a href="../UI/colors.html">Colors demo</a> to see both side by side on real components. It covers everywhere the palette paints text on its own colour: `.text-bg-*`, `.bg-gradient-*`, `.card-*` headers, `.direct-chat-*`, and the button, badge and pill text that `data-lte-primary` produces. Three things worth knowing:

- **It has to be black.** AdminLTE 3's own dark ink, `#1f2d3d`, only clears 4.5:1 on two of the eight — `teal` and `cyan`. The others need true black, which is what `aa` uses. The colours that already take dark text (`lime`, `orange`, `yellow`) keep `#1f2d3d` and are not touched.
- **Pair a flipped colour with `data-bs-theme="light"`.** A sidebar or header carrying one of the eight now has dark text, so its nav links should follow: `<nav class="app-header navbar navbar-expand text-bg-lightblue" data-bs-theme="light">`. The demo pages do this for you when the switch is on.
- **`.text-lightblue` and `.link-lightblue` are a different question.** Those paint the colour *as text on the page background*, where the same eight are also thin. Use the emphasis token instead — `.text-lightblue-emphasis` equivalents come from `--bs-lightblue-text-emphasis`, which is the colour shaded 60% and readable on a light background.

The attribute works with either sheet; on the designed palette it emits nothing at all, because every colour there clears AA with white text by construction — so it costs nothing to leave in your markup ([#6110](https://github.com/ColorlibHQ/AdminLTE/issues/6110)).

The v3 sheet is built from the same Sass as the designed one — `src/scss/adminlte-colors-v3.scss` sets `$lte-palette` to the v3 map and `$lte-palette-contrast: "yiq"` — so `$lte-palette-custom`, `$lte-palette-aliases` and the rest below apply to it too. Its skin presets are AdminLTE 2's: a coloured header over the v3 dark sidebar (`text-bg-gray-dark`, the exact `sidebar-dark-*` background) or over a white one:

<div class="table-responsive" tabindex="0">
  <table class="table align-middle">
    <thead>
      <tr>
        <th scope="col">Skin</th>
        <th scope="col">Header</th>
        <th scope="col">Sidebar</th>
        <th scope="col">Primary</th>
        <th scope="col">v2/v3 name</th>
      </tr>
    </thead>
    <tbody>
      {skinsV3.map(s => (
        <tr>
          <td class="text-nowrap fw-semibold">{s.name}</td>
          <td class="text-nowrap"><code>{s.header.cls}</code> · <code>{s.header.theme}</code></td>
          <td class="text-nowrap"><code>{s.sidebar.cls}</code> · <code>{s.sidebar.theme}</code></td>
          <td class="text-nowrap"><code>{s.primary}</code></td>
          <td class="text-body-secondary">{s.note}</td>
        </tr>
      ))}
    </tbody>
  </table>
</div>

##### The palette

Nine chromatic colours and five neutrals for chrome. Every one of them takes **white** text at 4.5:1 or better, so `.text-bg-*`, small boxes, badges and coloured sidebars all read the same way — no per-colour "is this one dark text?" question.

<div class="table-responsive">
  <table class="table align-middle">
    <thead>
      <tr>
        <th scope="col">Name</th>
        <th scope="col">Hex</th>
        <th scope="col">OKLCH</th>
        <th scope="col">White text</th>
        <th scope="col">Character &amp; use</th>
      </tr>
    </thead>
    <tbody>
      {palette.map(c => (
        <tr>
          <td class="text-nowrap">
            <span class={`badge text-bg-${c.name} me-2`}>&nbsp;&nbsp;&nbsp;</span>
            <code>{c.name}</code>
          </td>
          <td><code>{c.hex}</code></td>
          <td class="text-nowrap"><small class="font-monospace">{c.oklchLabel}</small></td>
          <td class="text-nowrap">{c.contrastWhite.toFixed(1)}:1</td>
          <td><span class="text-body-emphasis">{c.description}</span> <span class="text-body-secondary">{c.use}</span></td>
        </tr>
      ))}
    </tbody>
  </table>
</div>

The colours are named for what they are, not for Bootstrap's `$colors` names (`blue`, `purple`, `red`, …). Those overlap the theme colours and are deliberately not generated; the `--bs-blue`, `--bs-purple` … variables Bootstrap defines are still there if you want the raw values.

##### What you get

For each colour `<name>` the sheet emits the same shapes Bootstrap emits for a theme colour, so opacity modifiers and dark mode work exactly as you would expect:

| You get | Example | Notes |
|---|---|---|
| Tokens | `--bs-teal`, `--bs-teal-rgb` | Plus `--bs-teal-bg-subtle`, `--bs-teal-border-subtle`, `--bs-teal-text-emphasis` in both colour modes |
| Background | `.bg-teal`, `.bg-teal.bg-opacity-25` | Same variable shape as `.bg-primary` |
| Background + contrast text | `.text-bg-teal` | White text; this is what widgets use |
| Gradient | `.bg-gradient-teal` | The AdminLTE 3 class; also emitted for the 8 theme colours (`.bg-gradient-primary` …). Bootstrap's composable `.bg-teal.bg-gradient` gives the same result |
| Text, border, link | `.text-teal`, `.border-teal`, `.link-teal` | With `.text-opacity-*`, `.border-opacity-*`, `.link-underline-*` |
| Cards | `.card-teal`, `.card-outline.card-teal`, `.card.text-bg-teal` | Feeds the same `--lte-card-variant-*` variables the theme-colour cards use |
| Callouts | `.callout-teal` | Subtle background, emphasis text — flips in dark mode |
| Direct chat | `.direct-chat-teal` | |
| Widgets | `.small-box.text-bg-teal`, `.info-box.text-bg-teal`, `.badge.text-bg-teal`, `.progress-bar.bg-teal` | These already colour themselves through `.text-bg-*` / `.bg-*`, so nothing extra is emitted |
| Buttons | `.btn.text-bg-teal`, `.btn.bg-gradient-teal` | Get a hover / active shade so they don't look inert |

##### Making one of them your `primary`

Bootstrap compiles `$primary` into its components at build time: `.btn-primary` ships with `--bs-btn-bg: #0d6efd`, not `var(--bs-primary)`. So overriding the token repaints `.text-primary` and `.bg-primary` and leaves every button, pagination and checkbox blue — the reason a skin used to look half-applied ([#6107](https://github.com/ColorlibHQ/AdminLTE/issues/6107)).

Both colour sheets rewire those components to variables and point them at a palette colour with one attribute:

```html
<html lang="en" data-lte-primary="teal">
```

Nothing changes without the attribute, and it works on any container — a section of a page can carry its own primary. What follows the colour:

| Follows | Details |
|---|---|
| Buttons | `.btn-primary`, `.btn-outline-primary` (hover, active and disabled states shaded exactly as Bootstrap shades its own), `.btn-link` |
| Links | `a`, `.link-primary`, `.btn-link`, pagination links — with Bootstrap's dark-mode tint in dark mode |
| Utilities and tokens | `--bs-primary`, `--bs-primary-rgb`, the subtle / emphasis trio, `.text-primary`, `.bg-primary`, `.border-primary`, `.text-bg-primary`, `.alert-primary`, `.list-group-item-primary`, `.accordion` |
| Forms | Focus ring and focus border on `.form-control` / `.form-select`, `.form-check-input:checked`, indeterminate checkboxes, `.form-range` thumbs |
| Components | `.nav-pills .nav-link.active`, `.pagination .active`, `.progress-bar`, `.list-group-item.active`, `.dropdown-item.active`, `--bs-focus-ring-color` |
| AdminLTE | `.card-primary` (and `.card-outline`), `.direct-chat-primary`, `.toast-primary`, `.small-box`/`.info-box` carrying `.bg-primary` |

Bootstrap's own theme colours work too — `data-lte-primary="danger"` needs no palette colour at all. Two things stay Bootstrap-blue by design: `.table-primary`'s striped and hover tints, and the switch knob painted into a data-URI when a switch is checked.

For a brand colour that isn't in the palette, set the token yourself and keep the attribute:

```css
[data-lte-primary="brand"] {
  --bs-primary: #7a5af8;
  --bs-primary-rgb: 122, 90, 248;
  /* optional — otherwise the hover shade is computed with color-mix() */
  --lte-primary-hover: #6849e6;
  --lte-primary-active: #5b3fd1;
  /* dark text on a light brand colour */
  --lte-primary-color: #fff;
}
```

With Sass it is one line — `$lte-palette-custom: ("brand": #7a5af8)` (see [Adding your brand colours](#adding-your-brand-colours)) — and `data-lte-primary="brand"` is generated for you, shades and contrast text included.

Pick a colour that takes white text: the checkmark inside a checked checkbox is white in Bootstrap and cannot be recoloured from CSS. Every colour in the designed palette qualifies; in the v3 palette, `lime`, `yellow` and `orange` do not.

##### Skins — colouring the sidebar and header

In AdminLTE 4 a "skin" is not a class of its own: it is a background utility plus `data-bs-theme` on the sidebar and the header. `data-bs-theme="dark"` gives light nav links and brand text; `"light"` gives dark ones. Because every palette colour takes white text, a coloured sidebar or header always pairs with `dark`:

```html

<nav class="app-header navbar navbar-expand text-bg-sky" data-bs-theme="dark">…</nav>


<aside class="app-sidebar text-bg-navy shadow" data-bs-theme="dark">…</aside>
```

Each preset also names the `data-lte-primary` that goes with it, so the buttons match the chrome. Admin dashboards tend to offer their looks in four families, and the presets follow them. Each thumbnail is the real thing in miniature — sidebar, header, and the two accents that go with it. The <a href="../UI/colors.html">Colors demo</a> applies any preset to itself with one click; the <a href="../generate/theme.html">Theme Customize</a> page does the same and then lets you mix sidebar, navbar and footer freely (its pickers list the palette under "Extended palette").

{skinGroups.map(group => (
  <div class="mb-4">
    <h6 class="fw-semibold mb-1">{group.name}</h6>
    <p class="text-body-secondary mb-2">{group.note}</p>
    <div class="table-responsive" tabindex="0">
      <table class="table align-middle mb-0">
        <thead>
          <tr>
            <th scope="col" class="text-nowrap" style="width: 1%">Preview</th>
            <th scope="col">Skin</th>
            <th scope="col">Header</th>
            <th scope="col">Sidebar</th>
            <th scope="col">Primary</th>
            <th scope="col">Why</th>
          </tr>
        </thead>
        <tbody>
          {skins.filter(s => s.group === group.name).map(s => (
            <tr>
              <td><SkinPreview skin={s} size="sm" /></td>
              <td class="text-nowrap fw-semibold">{s.name}</td>
              <td class="text-nowrap"><code>{s.header.cls}</code> · <code>{s.header.theme}</code></td>
              <td class="text-nowrap"><code>{s.sidebar.cls}</code> · <code>{s.sidebar.theme}</code></td>
              <td class="text-nowrap"><code>{s.primary}</code></td>
              <td>{s.note}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  </div>
))}

`bg-body`, `bg-body-secondary` and `bg-body-tertiary` are Bootstrap's own surface utilities — they follow the element's `data-bs-theme`, which is why "Default" is a dark sidebar. Every `text-bg-*` and `bg-gradient-*` entry needs `adminlte-colors.css`.

##### Dashboard sets — which four to put on a widget row

Picking four colours for a row of small boxes is where palettes usually fall apart. These quartets are chosen by hue relationship rather than taste; use them as-is or as a starting point.

<div class="table-responsive">
  <table class="table align-middle">
    <thead>
      <tr>
        <th scope="col">Set</th>
        <th scope="col">Classes</th>
        <th scope="col">Why it works</th>
      </tr>
    </thead>
    <tbody>
      {sets.map(s => (
        <tr>
          <td class="text-nowrap fw-semibold">{s.name}</td>
          <td class="text-nowrap">{s.colors.map(c => <span class={`badge text-bg-${c} me-1`}>{c}</span>)}</td>
          <td>{s.why}</td>
        </tr>
      ))}
    </tbody>
  </table>
</div>

##### Adding your brand colours

**With Sass** (recommended): set `$lte-palette-custom` before importing the entry, and your colour gets every token, utility and component hook the shipped colours get. Names you reuse override the shipped value.

```scss
// your-adminlte.scss
$lte-palette-custom: (
  "brand": #0f766e,      // new colour → .bg-brand, .text-bg-brand, .card-brand, …
  "sky":   #0369a1       // re-tune a shipped colour to your blue
);
@import "admin-lte/src/scss/adminlte-colors";
```

That also generates `data-lte-primary="brand"`, so `<html data-lte-primary="brand">` turns Bootstrap's `primary` components into your brand colour with the right hover shades and contrast text.

To ship **only** your colours (drop the fourteen), set `$lte-palette` itself instead:

```scss
$lte-palette: (
  "brand": #0f766e,
  "brand-dark": #134e4a,
  "brand-warm": #b45309
);
@import "admin-lte/src/scss/adminlte-colors";
```

Two more knobs:

- `$lte-palette-aliases` — extra names that generate the same classes as an existing entry. AdminLTE 3 projects can bring their old class names back: `("lightblue": "sky", "maroon": "pink")`.
- `$lte-palette-min-contrast-ratio` — the contrast Bootstrap's `color-contrast()` requires when it picks the automatic text colour for `.text-bg-*`, `.bg-gradient-*` and `.card-*` headers. Default `4.5` (WCAG AA for normal text). If a custom colour can't reach it with white, the text flips to black; `3` (WCAG AA for large text and UI components) is the other defensible setting.
- `$lte-palette-contrast` — `"wcag"` (default) or `"yiq"`, the Bootstrap 4 / AdminLTE 3 brightness rule (`$lte-palette-yiq-threshold: 150`, dark text `#1f2d3d`). The v3 sheet uses `"yiq"`; set it if you want a custom palette to pick text colours the way v3 did.

**Choosing a brand colour that works.** The one property that matters is that white text on it reaches 4.5:1 — that is what keeps `.text-bg-brand`, small boxes and a branded sidebar readable, and what the accessibility gate on the demo pages checks. Most brand blues, greens and purples pass; bright oranges, yellows and cyans don't. The <a href="../UI/colors.html#brand-hex">checker on the demo page</a> reports the ratio, offers a same-hue version darkened just enough to pass, warns when your hue sits on top of an existing one, and prints the Sass snippet.

**Without Sass**, on the prebuilt CSS, you can still hand-write one colour. This is the whole shape for a single name:

```css
:root, [data-bs-theme="light"] {
  --bs-brand: #0f766e;
  --bs-brand-rgb: 15, 118, 110;
  --bs-brand-text-emphasis: #062f2c;
  --bs-brand-bg-subtle: #cfe4e2;
  --bs-brand-border-subtle: #9fc8c5;
}
[data-bs-theme="dark"] {
  --bs-brand-text-emphasis: #6fada8;
  --bs-brand-bg-subtle: #031816;
  --bs-brand-border-subtle: #094742;
}
.bg-brand      { --bs-bg-opacity: 1; background-color: rgba(var(--bs-brand-rgb), var(--bs-bg-opacity)) !important; }
.text-bg-brand { color: #fff !important; background-color: rgba(var(--bs-brand-rgb), var(--bs-bg-opacity, 1)) !important; }
.text-brand    { --bs-text-opacity: 1; color: rgba(var(--bs-brand-rgb), var(--bs-text-opacity)) !important; }
.border-brand  { --bs-border-opacity: 1; border-color: rgba(var(--bs-brand-rgb), var(--bs-border-opacity)) !important; }
.card-brand, .bg-brand, .text-bg-brand {
  --lte-card-variant-bg: #0f766e; --lte-card-variant-bg-rgb: 15, 118, 110;
  --lte-card-variant-color: #fff; --lte-card-variant-color-rgb: 255, 255, 255;
}
```

##### How the palette was designed

The fourteen values are generated, not picked. `scripts/palette.mjs` derives them in **OKLCH** — the perceptually uniform colour space Tailwind v4 and Radix build their palettes in — from three rules:

<div class="row g-4 align-items-start my-2">
  <div class="col-lg-5">
    <PaletteWheel />
    <p class="small text-body-secondary mt-2 mb-0">Angle is hue, distance from the centre is chroma. Filled dots: the palette. Hollow rings: Bootstrap's <em>primary · success · danger · warning · info</em> (secondary sits unlabelled at the centre). The unlabelled dots near the centre are the five neutrals — navy, steel, slate, graphite, midnight.</p>
  </div>
  <div class="col-lg-7">
    <ol class="mb-0">
      <li class="mb-3"><strong>Equal contrast, by construction.</strong> Each chromatic colour is the <em>lightest</em> value of its hue whose white text still reaches 4.6:1. That puts every one at OKLCH L ≈ 0.55–0.59 — the same visual weight as Bootstrap's own primary, success and danger — so a row of mixed widgets never has one colour shouting.</li>
      <li class="mb-3"><strong>Hues in the gaps.</strong> Bootstrap already owns red (21°), yellow (85°), green (157°), cyan (218°) and blue (260°). The palette hues sit at least 17° from each of them and 20° from each other — orange 42 · amber 67 · olive 126 · teal 190 · sky 237 · indigo 283 · violet 303 · fuchsia 324 · pink 350 — so mixing palette and theme colours on one page is safe.</li>
      <li class="mb-0"><strong>Chroma capped, neutrals tinted.</strong> Chroma is capped at 0.20 (parity with primary 0.23 / danger 0.20), so indigo and violet don't overpower teal and amber, which sit at the sRGB gamut limit for this lightness. The five neutrals — navy, steel, slate, graphite, midnight — are cool-tinted (hue ≈ 255–287°) rather than pure grey, the way modern UI neutrals are built, and sit at the lightness steps popular admin kits use for their sidebars (L ≈ 0.24, 0.30, 0.33, 0.40, 0.50).</li>
    </ol>
  </div>
</div>

`npm run palette` regenerates the map from those targets; `npm run palette -- --check` (and the unit tests) verify contrast and hue separation, and that the Sass map and the demo data agree.

##### Accessibility and dark mode

- Every colour in the **designed** palette clears 4.5:1 with white text (the lowest is 4.6:1), so `.text-bg-*` never needs a per-colour exception and the demo pages using them pass the axe gate.
- The **v3** palette is the historical set, and eight of its eighteen colours miss 4.5:1 with the text v3 assigned them. `data-lte-contrast="aa"` flips exactly those — see [Making the v3 colours pass AA](#making-the-v3-colours-pass-aa).
- Solid utilities (`.bg-*`, `.text-bg-*`) are the same in both colour modes, as Bootstrap's own are. The subtle tokens — and with them `.callout-*` — flip for `data-bs-theme="dark"` using the same derivation Bootstrap applies to `.alert-*`.
- `.text-*` and `.link-*` on a coloured background are your responsibility, exactly as with Bootstrap's theme colours.

##### Migrating from AdminLTE 3

v3's extended colours were `lightblue`, `navy`, `olive`, `lime`, `fuchsia`, `maroon` plus Bootstrap 4's named palette, several of them with white text below 4:1. The new palette keeps the useful names (`navy`, `olive`, `fuchsia`) and remakes the rest:

| AdminLTE 3 | Now | |
|---|---|---|
| `bg-lightblue` | `text-bg-sky` | Same hue, set so white text passes |
| `bg-maroon` | `text-bg-pink` | |
| `bg-navy` / `bg-olive` / `bg-fuchsia` | same names | Values redesigned |
| `bg-lime` | — | No white-text lime exists; use `text-bg-olive` or Bootstrap's `text-bg-warning` |
| `bg-purple`, `bg-teal`, `bg-orange`, `bg-indigo` … | `text-bg-violet`, `text-bg-teal`, `text-bg-orange`, `text-bg-indigo` | See the palette table |
| `sidebar-dark-primary`, `navbar-light` … | `text-bg-*` + `data-bs-theme` | See <em>Skins</em> above |
| `bg-gradient-*` | `bg-gradient-*` | Kept, for palette and theme colours |

Two ways to keep existing markup working: load **`adminlte-colors-v3.css`** and every v3 name is back with its original value (see [Two palettes](#two-palettes)), or use the designed palette with the alias one-liner `$lte-palette-aliases: ("lightblue": "sky", "maroon": "pink");`.

---

### Src Html Components Docs Customization (`src/html/components/docs/customization.mdx`)

AdminLTE inherits Bootstrap 5.3's CSS-custom-properties theming model and adds its own layer of SCSS variables on top. You can customize the look two ways:

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

##### Quick wins with CSS variables

Bootstrap and AdminLTE expose dozens of CSS custom properties on `:root`. Override them in your own stylesheet to retheme without rebuilding:

```css
:root,
[data-bs-theme="light"] {
  --bs-primary: #6610f2;        /* swap Bootstrap's blue for purple */
  --bs-primary-rgb: 102, 16, 242;
  --bs-body-bg: #f3f4f6;
}

[data-bs-theme="dark"] {
  --bs-body-bg: #14171c;        /* darker than Bootstrap's default */
  --bs-body-color: #e9ecef;
}
```

Components built on `var(--bs-primary)` (most of AdminLTE's chrome) pick this up automatically.

###### Common variables worth knowing

| Variable | Default | What it controls |
|---|---|---|
| `--bs-primary` | `#0d6efd` | Brand colour, used in active sidebar items, primary buttons |
| `--bs-body-bg` | `#fff` (light) / `#212529` (dark) | Page background |
| `--bs-body-color` | `#212529` | Default text colour |
| `--bs-border-color` | `#dee2e6` | All hairline borders |
| `--bs-border-radius` | `.375rem` | Default radius for cards, buttons, inputs |
| `--bs-border-radius-lg` | `.5rem` | Larger radius, used for modals |
| `--bs-font-sans-serif` | `system-ui, ...` | Default font stack |

For the full list, see [Bootstrap CSS variables](https://getbootstrap.com/docs/5.3/customize/css-variables/).

Need more than Bootstrap's eight theme colours — a navy sidebar, a teal widget row, your brand's blue as a first-class `.text-bg-brand`? That is what the opt-in [extended palette](colors.html) is for: twelve designed colours plus a one-line Sass hook (`$lte-palette-custom`) for your own, in a separate stylesheet so it costs nothing unless you load it.

##### SCSS variables

If you're building from source (cloned the repo or installed via npm + your own bundler), edit [`src/scss/_variables.scss`](https://github.com/ColorlibHQ/AdminLTE/blob/master/src/scss/_variables.scss) and recompile.

The most-overridden variables, with their defaults:

```scss
// Sidebar
$lte-sidebar-width:          250px;       // collapse to mini at ~70px
$lte-sidebar-breakpoint:     lg;          // breakpoint at which sidebar starts off-canvas
$lte-sidebar-padding-x:      .5rem;
$lte-sidebar-padding-y:      .5rem;

// Sidebar colours (light theme)
$lte-sidebar-color:          $gray-800;
$lte-sidebar-hover-bg:       rgba($black, .1);
$lte-sidebar-active-color:   $black;
$lte-sidebar-menu-active-bg: rgba($black, .1);

// Header
$lte-app-header-height:      $nav-link-height + ($lte-app-header-link-padding-y * 2);
$lte-app-header-bottom-border-color: var(--#{$prefix}border-color);

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

AdminLTE changes about ten Bootstrap defaults in [`src/scss/_bootstrap-overrides.scss`](https://github.com/ColorlibHQ/AdminLTE/blob/master/src/scss/_bootstrap-overrides.scss); every other Bootstrap variable (button colours, spacing scale, breakpoints) keeps its stock default. All values use `!default`, so override them in your own SCSS file *before* the import:

```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";
```

> **Note:** compiling the Sass source requires `bootstrap` to be installed —
> it is a peer dependency of `admin-lte` since 4.1 (npm installs it
> automatically alongside). Make sure your Sass compiler can resolve bare
> `bootstrap/...` imports by adding `node_modules` to its load paths, e.g.
> `sass --load-path=node_modules` or, in Vite,
> `css: { preprocessorOptions: { scss: { loadPaths: ["node_modules"] } } }`.

##### Recompiling SCSS

If you cloned the repo, the build pipeline is already wired:

```bash
npm install
npm run css           # one-off build → dist/css/
npm run watch-css-main  # rebuild on save during development
```

For a project that uses AdminLTE as a dependency, point your bundler at `node_modules/admin-lte/src/scss/adminlte.scss`. Examples:

```js
// Vite / Rollup / Webpack
import "admin-lte/src/scss/adminlte.scss"
```

##### Custom sidebar branding

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

```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>
```

To swap the logo when the sidebar collapses to its mini state, AdminLTE provides a built-in pattern — see the [Logo Switch layout demo](../layout/logo-switch.html) for the markup.

##### Dark mode customization

AdminLTE follows Bootstrap 5.3's `data-bs-theme` attribute system. To customize dark-mode colours, scope your variables under `[data-bs-theme="dark"]`:

```css
[data-bs-theme="dark"] {
  --bs-primary: #4dabf7;          /* lighter primary for dark backgrounds */
  --lte-sidebar-color: #e9ecef;
  --lte-sidebar-active-color: #fff;
}
```

The [Color Mode](color-mode.html) page covers the toggle markup and JavaScript that switches the attribute.

##### Custom sidebar colour scheme

AdminLTE ships its sidebar styled for light mode, but the demos apply `data-bs-theme="dark"` on `.app-sidebar` to get the dark sidebar variant against a light page. You can mix and match:

```html

<body class="bg-body-tertiary">
  <aside class="app-sidebar bg-body-secondary shadow" data-bs-theme="dark">
    ...
  </aside>
</body>
```

```html

<body class="bg-body-tertiary">
  <aside class="app-sidebar bg-body shadow border-end">
    ...
  </aside>
</body>
```

##### Compact mode

Add `.compact-mode` to `.app-wrapper` (or anywhere up the tree) to reduce padding throughout the layout — handy for data-dense dashboards. The dimensions for compact mode come from the `*-compact` variables in `_variables.scss`.

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

##### When to recompile vs. override at runtime

| Customization | CSS variables | SCSS rebuild |
|---|---|---|
| Brand colours | ✅ | ✅ |
| Body / text colours | ✅ | ✅ |
| Border radius | ✅ | ✅ |
| Sidebar width | ❌ (not a CSS var) | ✅ |
| Breakpoint changes | ❌ | ✅ |
| Spacing scale | ❌ | ✅ |
| Font sizes | partial | ✅ (full control) |

Use CSS variables for everything you can — it survives AdminLTE upgrades without merge conflicts. Reserve SCSS overrides for structural changes (sidebar width, breakpoints) that CSS variables don't cover.

---

### Src Html Components Docs Deployment (`src/html/components/docs/deployment.mdx`)

AdminLTE is a static HTML/CSS/JS template — there's nothing exotic about deploying it. But there are a handful of choices that meaningfully change page weight, time-to-interactive, and CDN reliability. This page documents the ones worth getting right.

##### What to deploy

The deployable artefact is everything under `dist/` plus your own HTML / server-rendered templates:

```text
dist/
├── css/                ← stylesheets (LTR + RTL, dev + minified)
│   ├── adminlte.css
│   ├── adminlte.min.css
│   ├── adminlte.rtl.css
│   └── adminlte.rtl.min.css
├── js/                 ← JavaScript bundle (dev + minified)
│   ├── adminlte.js
│   └── adminlte.min.js
└── assets/             ← demo images / fonts (replace with your own)
```

You don't ship `src/`, `node_modules/`, or the demo HTML if you're embedding AdminLTE into your own app.

##### Always use the `.min` variants in production

The minified builds are the same code with whitespace stripped and identifiers shortened. For `adminlte.css` the saving is roughly 15-20%; for `adminlte.js` it's bigger (about 30% after gzip). The non-minified files are useful in development for readable source-map debugging — never serve them to users.

| Asset | Dev | Production |
|---|---|---|
| Stylesheet | `dist/css/adminlte.css` | **`dist/css/adminlte.min.css`** |
| Script | `dist/js/adminlte.js` | **`dist/js/adminlte.min.js`** |
| RTL stylesheet | `dist/css/adminlte.rtl.css` | **`dist/css/adminlte.rtl.min.css`** |
| Select2 theme *(optional)* | `dist/css/adminlte-select2.css` | **`dist/css/adminlte-select2.min.css`** |

`adminlte-select2.css` is opt-in and only needed if your app loads [Select2](integrations.html#select2-compatibility-theme); it is not part of `adminlte.css`. `adminlte-docs.css` styles the documentation pages only — never ship it.

##### Compression at the edge

Make sure your web server / CDN gzips (or brotlis) responses for the relevant content types:

```nginx
# nginx
gzip on;
gzip_types text/css application/javascript text/html image/svg+xml;
gzip_comp_level 6;
```

```apache
# Apache
AddOutputFilterByType DEFLATE text/css application/javascript text/html image/svg+xml
```

Cloudflare, Vercel, Netlify, and most managed hosting do this automatically. Verify with `curl -I -H "Accept-Encoding: gzip" your-page-url` and look for `content-encoding: gzip` in the response.

##### Cache headers

The dist artefacts have content hashes in their filenames *only if you wire it up via your bundler* — AdminLTE itself ships fixed filenames. Two reasonable strategies:

1. **Short cache + version query string** (simple, works without a build pipeline):

   ```html
   <link rel="stylesheet" href="/dist/css/adminlte.min.css?v=4.0.0" />
   <script src="/dist/js/adminlte.min.js?v=4.0.0"></script>
   ```

   Set `Cache-Control: public, max-age=86400` (1 day) at the server. Bump `?v=` on every release to force cache invalidation.

2. **Fingerprinted filenames** (production-grade, requires a bundler):

   Let your bundler emit `adminlte.[hash].min.css` and reference that hash in your HTML. Set `Cache-Control: public, max-age=31536000, immutable` (1 year). This is what Vite/Webpack/Rollup do by default if you import AdminLTE through them.

##### CDN integrity hashes

If you load AdminLTE from a public CDN (jsDelivr, unpkg), include a Subresource Integrity (SRI) hash so a compromised CDN can't serve malicious JavaScript silently:

```html
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/admin-lte@4.10.1/dist/css/adminlte.min.css"
  integrity="sha384-PASTE_THE_HASH_HERE"
  crossorigin="anonymous"
/>
```

You can get the hash from the [jsDelivr file detail page](https://www.jsdelivr.com/package/npm/admin-lte) — click any file and copy the "SRI" line. Do the same for Bootstrap, Popper, OverlayScrollbars, and Bootstrap Icons.

For private-hosted assets, generate the hash yourself:

```bash
openssl dgst -sha384 -binary dist/css/adminlte.min.css | openssl base64 -A
```

##### Loading order matters

Get the script order right or things break silently:

```html
<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.16.0/styles/overlayscrollbars.min.css" />

  
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@4.10.1/dist/css/adminlte.min.css" />
</head>

<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.16.0/browser/overlayscrollbars.browser.es6.min.js"></script>

  
  <script src="https://cdn.jsdelivr.net/npm/admin-lte@4.10.1/dist/js/adminlte.min.js"></script>
</body>
```

##### Defer or async?

The AdminLTE bundle waits for `DOMContentLoaded` before wiring its data API, so `defer` is safe and recommended:

```html
<script defer src="/dist/js/adminlte.min.js"></script>
```

Don't use `async` — the script could execute before its dependencies (Bootstrap, Popper) have parsed, and listeners would attach to elements that don't exist yet.

##### Critical CSS

For sub-1-second first paint, inline the styles that the above-the-fold layout uses (header + brand + first visible content) and load the rest of `adminlte.min.css` asynchronously:

```html
<style>/* extracted critical CSS for /dashboard */</style>

<link
  rel="preload"
  as="style"
  href="/dist/css/adminlte.min.css"
  onload="this.onload=null;this.rel='stylesheet'"
/>
<noscript><link rel="stylesheet" href="/dist/css/adminlte.min.css" /></noscript>
```

Extract critical CSS with a tool like [Critters](https://github.com/GoogleChromeLabs/critters) or [Penthouse](https://github.com/pocketjoso/penthouse) as part of your build. For dashboards behind a login (where first-load speed matters less than for marketing pages), this is usually overkill.

##### Preload key assets

Push critical resources to the browser before it discovers them in the HTML:

```html
<link rel="preload" as="style" href="/dist/css/adminlte.min.css" />
<link rel="preload" as="font" href="/fonts/source-sans-3.woff2" type="font/woff2" crossorigin />
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
```

Only preload what's used on every page. Preloading per-route assets in a shared layout wastes bandwidth.

##### Lazy-loading sub-pages

If your dashboard has many routes and the JS is server-rendered HTML per route, prefer per-route CSS imports rather than one mega-stylesheet. AdminLTE's core CSS is small (~44KB gzipped) but page-specific styles (charts, calendars, tables) can add up — only load them where they're used.

For SPAs, code-split heavy integrations:

```js
// Don't bundle the calendar app into the main chunk
const Calendar = () => import("./Calendar.vue")
```

##### Bundle size budget

The current published `dist/` artefacts gzip to:

| Asset | Size (gzip) |
|---|---|
| `adminlte.min.css` | ~40 KB |
| `adminlte.rtl.min.css` | ~40 KB |
| `adminlte.min.js` | ~5 KB |

The full first-page payload (HTML + Bootstrap + AdminLTE + Bootstrap Icons + Popper + OverlayScrollbars) is around **180 KB gzipped** when loaded from CDN. The CI build enforces a `bundlewatch` check that fails if any asset crosses its budget.

##### Service workers / offline support

AdminLTE is just static files — caching them in a service worker works without changes. A minimal Workbox config:

```js
import { precacheAndRoute } from "workbox-precaching"

precacheAndRoute([
  { url: "/dist/css/adminlte.min.css", revision: "4.0.0" },
  { url: "/dist/js/adminlte.min.js", revision: "4.0.0" },
  // ...your own pages
])
```

##### Removing dev-only files before deploy

If you're deploying the demo `dist/` folder verbatim, strip these patterns from the upload:

- `*.map` (source maps — leak source code to anyone who opens devtools)
- `**/html/` (the Astro intermediate build, if present)
- `**/.astro/` (Astro cache)

For `rclone` / `rsync`:

```bash
rsync -avz --delete \
  --exclude='*.map' \
  --exclude='html/' \
  --exclude='.astro/' \
  dist/ user@server:/var/www/your-app/
```

##### A note on Cloudflare cache

Cloudflare's default Browser Cache TTL is 4 hours. If you push a new release and want users to see it immediately, [purge the affected paths](https://developers.cloudflare.com/cache/how-to/purge-cache/) from the Cloudflare dashboard or API. Otherwise, users on cached pages will see your old CSS / JS for up to four hours.

##### Where to next

- [Customization](customization.html) — change colours, sidebar width, breakpoints before you deploy
- [Recommended Integrations](integrations.html) — pick lightweight libraries for the things AdminLTE doesn't bundle
- [Accessibility](javascript/accessibility.html) — make sure your deployed pages keep their a11y guarantees

---

### Src Html Components Docs How To Contribute (`src/html/components/docs/how-to-contribute.mdx`)

# Contributing to AdminLTE

Contributions are always **welcome and recommended**! Here is how for beginner's: [Get started with open source click here](https://youtu.be/GbqSvJs-6W4)

1. Contribution Requirements :
    * When you contribute, you agree to give a non-exclusive license to AdminLTE.io to use that contribution in any context as we (AdminLTE.io) see appropriate.
    * If you use content provided by another party, it must be appropriately licensed using an [open source](https://opensource.org/licenses) license.
    * Contributions are only accepted through GitHub pull requests.
    * Finally, contributed code must work in all supported browsers (see above for browser support).
2. Installation :
    * Fork the repository ([here is the guide](https://help.github.com/articles/fork-a-repo/)).
    * Clone to your machine

    ```bash
    git clone https://github.com/YOUR_USERNAME/AdminLTE.git
    ```
    * Create a new branch from `master`
3. Compile dist files (Development) :
    * To compile the dist files you need Node.js 22.22.3+, 24.16.0+, or 26.3.0+ and npm 10+
    * `npm ci` (install the locked dependency tree)
    * `npm run dev` (developer mode, asset watchers and Astro live demo at http://localhost:3000)
    * Make your changes only in `./src` Folder OR `package.json` in any files which are necessary for contribution
    * Do not make changes in `./dist/**` Because it contains compiled files and do not include in PR (Pull Request)
    * `npm test` (unit tests), then `npm run build` and `npm run test-browser` / `npm run test-a11y` (install browsers with `npx playwright install chromium`)
    * `npm run production` (lint, run unit tests, compile assets, and check gzip budgets before creating a pull request)
4. Create a pull request to `master` branch

## Online one-click setup for contributing

You can use [Codespace](https://docs.github.com/en/codespaces) an online IDE which is free for Open Source for working on issues or making PRs (Pull Requests). With a single click it will launch a workspace and automatically:

- clone the `AdminLTE` repo.
- Open with [Codespace](https://docs.github.com/en/codespaces) or [](https://gitpod.io/from-referrer/)
- install the dependencies.
- run `npm run dev` to start the server.

---

### Src Html Components Docs Integrations (`src/html/components/docs/integrations.mdx`)

AdminLTE deliberately ships a lean dependency tree. Rather than bundling every form widget or chart library, we recommend best-in-class third-party libraries you can drop in when you need them. Every library below is **MIT-licensed**, **actively maintained**, and **jQuery-free**.

Each section has a CDN-based "install" snippet and the minimum JavaScript needed to wire it up. You can swap CDN URLs for npm installs if you're using a bundler.

> **About the version numbers below:** every CDN URL pins a specific version (eg. `@4.6.13`) so the snippets stay reproducible. They were current as of the latest release, but third-party libraries publish new versions independently of AdminLTE. Before copying a snippet into production, check the library's homepage (linked in each section) for the latest version — and consider hosting a copy yourself rather than relying on the CDN long-term.

##### Form widgets

###### Date / time picker — Flatpickr

[Flatpickr](https://flatpickr.js.org/) is a lightweight, no-dependency date and time picker.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.css" />
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13"></script>

<input type="text" class="form-control" id="datepicker" placeholder="Select a date" />

<script>
  flatpickr("#datepicker", {
    altInput: true,
    altFormat: "F j, Y",
    dateFormat: "Y-m-d"
  })
</script>
```

Supports date ranges, time-only mode, multiple dates, and i18n out of the box.

###### Multi-select / tag input — Tom Select

[Tom Select](https://tom-select.js.org/) is a vanilla-JS rewrite of Selectize. Great fit for searchable selects, tag inputs, and remote-data autocomplete.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tom-select@2.6.2/dist/css/tom-select.bootstrap5.min.css" />
<script src="https://cdn.jsdelivr.net/npm/tom-select@2.6.2/dist/js/tom-select.complete.min.js"></script>

<select id="tags" class="form-control" multiple placeholder="Pick some tags">
  <option value="design">Design</option>
  <option value="dev">Development</option>
  <option value="qa">QA</option>
</select>

<script>
  new TomSelect("#tags", { plugins: ["remove_button"] })
</script>
```

Ships a `tom-select.bootstrap5.min.css` theme that matches Bootstrap form controls.

**Remote data.** For selects backed by a database rather than a static option list, fetch inside `load()`. Tom Select debounces it for you via `loadThrottle` (300 ms by default), and the `virtual_scroll` plugin pages through results as the user scrolls the dropdown:

```js
let inFlight = null

new TomSelect("#reviewer", {
  valueField: "id",
  labelField: "name",
  searchField: ["name"],
  loadThrottle: 300,                      // debounce, built in
  plugins: ["virtual_scroll"],            // infinite scroll pagination
  firstUrl: (query) => `/api/users?q=${encodeURIComponent(query)}&page=1`,
  load(query, callback) {
    inFlight?.abort()                     // drop the stale request
    inFlight = new AbortController()

    fetch(this.getUrl(query), { signal: inFlight.signal })
      .then((response) => response.json())
      .then((json) => {
        if (json.next) {
          this.setNextUrl(query, json.next)
        }
        callback(json.results)
      })
      .catch((error) => {
        if (error.name !== "AbortError") {
          callback()
        }
      })
  }
})
```

`forms/advanced.html` runs this exact pattern against a stubbed endpoint — swap the stub for a `fetch()` and it works unchanged.

###### Coming from Select2

[Select2](https://select2.org/) is still maintained (4.1.0 shipped in 2026), but it is still a jQuery plugin — its UMD wrapper hard-requires jQuery, and there is no jQuery-free build. AdminLTE 4 therefore recommends Tom Select instead, which covers the same feature set for roughly 20 kB gzip against Select2's ~50 kB once jQuery is counted.

The API translates directly:

| Select2 | Tom Select |
|---|---|
| `$("#x").select2()` | `new TomSelect("#x")` |
| `$("#x").select2({ tags: true })` | `new TomSelect("#x", { create: true })` |
| `allowClear: true` | `plugins: ["clear_button"]` |
| `closeOnSelect: false` | `closeAfterSelect: false` |
| `maximumSelectionLength: 3` | `maxItems: 3` |
| `minimumInputLength: 2` | `shouldLoad: (q) => q.length >= 2` |
| `ajax: { url, delay: 250 }` | `load(query, callback)` + `loadThrottle: 250` |
| `ajax: { …, processResults }` | `valueField` / `labelField` / `searchField` |
| infinite scroll (`pagination.more`) | `plugins: ["virtual_scroll"]` + `setNextUrl()` |
| `templateResult` / `templateSelection` | `render: { option, item }` |
| `dropdownParent: $("#modal")` | *(not needed — the dropdown is inline)* |
| `$("#x").val("2").trigger("change")` | `ts.setValue("2")` |
| `$("#x").val()` | `ts.getValue()` |
| `$("#x").on("select2:select", fn)` | `ts.on("item_add", fn)` |
| `$("#x").on("change", fn)` | `ts.on("change", fn)` |
| `$("#x").select2("open")` | `ts.open()` |
| `$("#x").prop("disabled", true)` | `ts.disable()` |
| `$("#x").select2("destroy")` | `ts.destroy()` |
| `$("#x").empty().select2({ data })` | `ts.clearOptions(); ts.addOptions(data)` |

A side-by-side version of this table with runnable code lives on the [Advanced Elements](../forms/advanced.html#select2) demo page.

**If you have to keep Select2**, AdminLTE ships a *styling-only* compatibility sheet — see [Select2 compatibility theme](#select2-compatibility-theme) below.

###### Select2 compatibility theme

Some projects can't drop Select2: an ERP with hundreds of server-rendered forms, a vendor plugin that initialises it for you, or a team that simply isn't ready. AdminLTE won't bundle Select2 or jQuery, but it does ship a **CSS-only compatibility theme** so a Select2 you bring yourself inherits Bootstrap 5.3's form metrics, focus ring, validation states and `data-bs-theme` dark mode.

- **`dist/css/adminlte-select2.css`** (+ `.min.css`, `.rtl.css`, `.rtl.min.css`)
- No JavaScript, no jQuery, nothing added to `adminlte.css` — you opt in by loading one extra stylesheet.

Load it **after** `select2.css`:

```html
<link rel="stylesheet" href="path/to/adminlte.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0/dist/css/select2.min.css" />
<link rel="stylesheet" href="path/to/adminlte-select2.min.css" />

<select id="country" class="form-select">
  <option value="">Select a country…</option>
  <option value="lv">Latvia</option>
</select>

<script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0/dist/js/select2.min.js"></script>
<script>
  $("#country").select2({ width: "100%" })
</script>
```

What the theme handles for you:

| | |
|---|---|
| Metrics | Height, padding, font size and border radius match `.form-select` exactly |
| Sizing | `.form-select-sm` / `.form-select-lg` on the original `<select>` are honoured |
| Focus | Bootstrap's focus ring instead of Select2's black outline |
| Dark mode | Follows `data-bs-theme` — including the body-appended dropdown |
| Validation | `.is-invalid` / `.is-valid` on the original `<select>` colour the container |
| Multi-select | Choice pills styled as Bootstrap secondary badges |
| Input groups | `.input-group` corner rounding and flex sizing |
| Modals | Dropdown `z-index` raised above `.modal` — fixes the single most-reported Select2 + Bootstrap 5 bug, so `dropdownParent` is no longer needed |
| RTL | Generated automatically alongside the rest of the CSS |

Using a bundler? The compiled sheet is reachable through the package's `exports` map — again, after Select2's own CSS:

```js
import "admin-lte/dist/css/adminlte.min.css"
import "select2/dist/css/select2.min.css"
import "admin-lte/dist/css/adminlte-select2.min.css"
```

Compiling your own Sass instead? Import the partial after AdminLTE, which has already loaded the Bootstrap variables and mixins it needs:

```scss
@import "node_modules/admin-lte/src/scss/adminlte";
@import "node_modules/admin-lte/src/scss/compat/select2";
```

The theme tracks Select2 4.1.x markup (it also covers the 4.0.x choice-remove markup). It is styling only — bugs in Select2 itself are still Select2's to fix, and none of the official AdminLTE framework ports (Vue, React, Django, Laravel) can consume a jQuery plugin.

###### Range slider — noUiSlider

[noUiSlider](https://refreshless.com/nouislider/) handles single, range, and multi-handle sliders with no dependencies.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nouislider@15.8.1/dist/nouislider.min.css" />
<script src="https://cdn.jsdelivr.net/npm/nouislider@15.8.1/dist/nouislider.min.js"></script>

<div id="slider"></div>

<script>
  noUiSlider.create(document.getElementById("slider"), {
    start: [20, 80],
    connect: true,
    range: { min: 0, max: 100 }
  })
</script>
```

###### Color picker — Pickr

[Pickr](https://github.com/Simonwep/pickr) is a flat, modern color picker with multiple themes.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/pickr@1.10.2/dist/themes/classic.min.css" />
<script src="https://cdn.jsdelivr.net/npm/@simonwep/pickr@1.10.2/dist/pickr.min.js"></script>

<div id="color-picker"></div>

<script>
  Pickr.create({
    el: "#color-picker",
    theme: "classic",
    default: "#0d6efd",
    components: { preview: true, opacity: true, hue: true, interaction: { input: true, save: true } }
  })
</script>
```

###### Input mask — IMask

[IMask](https://imask.js.org/) handles phone numbers, credit cards, dates, currency — anything with a fixed pattern.

```html
<script src="https://cdn.jsdelivr.net/npm/imask@7.6.1/dist/imask.min.js"></script>

<input class="form-control" id="phone" placeholder="(123) 456-7890" />

<script>
  IMask(document.getElementById("phone"), { mask: "(000) 000-0000" })
</script>
```

##### File handling

###### File uploader — Dropzone

[Dropzone](https://www.dropzone.dev/) is the de-facto drag-and-drop uploader. The v6 rewrite is plain JavaScript (the older v5 had jQuery-coupled examples; v6+ does not).

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dropzone@6.3.5/dist/dropzone.css" />
<script src="https://cdn.jsdelivr.net/npm/dropzone@6.3.5/dist/dropzone-min.js"></script>

<form action="/upload" class="dropzone" id="uploader"></form>

<script>
  Dropzone.autoDiscover = false
  new Dropzone("#uploader", { paramName: "file", maxFilesize: 5 })
</script>
```

###### File uploader — FilePond

[FilePond](https://pqina.nl/filepond/) is a more modern alternative with a polished UX and a plugin system for image preview, validation, EXIF, etc.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/filepond@4.32.12/dist/filepond.min.css" />
<script src="https://cdn.jsdelivr.net/npm/filepond@4.32.12/dist/filepond.min.js"></script>

<input type="file" class="filepond" />

<script>
  FilePond.create(document.querySelector(".filepond"), { allowMultiple: true })
</script>
```

##### Rich text & markdown editors

###### Rich text — Quill

[Quill](https://quilljs.com/) is the best-supported open-source WYSIWYG editor. Lightweight, modular, no dependencies.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" />
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>

<div id="editor" style="height: 12rem;"></div>

<script>
  new Quill("#editor", {
    theme: "snow",
    placeholder: "Write something fancy&hellip;"
  })
</script>
```

###### Markdown — EasyMDE

[EasyMDE](https://github.com/Ionaru/easy-markdown-editor) is the actively-maintained fork of SimpleMDE. Same look, same API, but still gets updates.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde@2.21.0/dist/easymde.min.css" />
<script src="https://cdn.jsdelivr.net/npm/easymde@2.21.0/dist/easymde.min.js"></script>

<textarea id="markdown"></textarea>

<script>
  new EasyMDE({ element: document.getElementById("markdown") })
</script>
```

###### WYSIWYG + Markdown — Toast UI Editor

[Toast UI Editor](https://ui.toast.com/tui-editor) supports both markdown and WYSIWYG modes side-by-side. Best when you want users to pick their preferred mode.

```html
<link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.min.css" />
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>

<div id="md-editor"></div>

<script>
  new toastui.Editor({
    el: document.getElementById("md-editor"),
    height: "20rem",
    initialEditType: "markdown",
    previewStyle: "vertical"
  })
</script>
```

##### Charts

###### Chart.js

[Chart.js](https://www.chartjs.org/) (MIT) is the chart library used in the AdminLTE dashboards and on the `charts/chartjs.html` showcase. Canvas-based, animated, responsive, with line, area, bar, doughnut, pie, polar-area, radar, scatter and bubble charts built in.

```html
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>


<div style="position: relative; height: 300px">
  <canvas id="bar-chart" role="img" aria-label="Revenue per quarter"></canvas>
</div>

<script>
  new Chart(document.getElementById("bar-chart"), {
    type: "bar",
    data: {
      labels: ["Q1", "Q2", "Q3", "Q4"],
      datasets: [{ label: "Revenue", data: [120, 190, 170, 230] }]
    },
    options: { maintainAspectRatio: false }
  })
</script>
```

The demo pages style every chart from AdminLTE's CSS variables — font, text and gridline colours, tooltip and legend — and restyle them live when the colour mode changes. That preset is a short inline script; see [Charts that follow dark mode](recipes.html#charts-that-follow-dark-mode) to copy it into your own pages.

##### Data tables

###### Tabulator

[Tabulator](https://tabulator.info/) is the jQuery-free DataTables alternative used in `tables/data.html`. Sort, filter, paginate, edit, export — all built in.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tabulator-tables@6.6.1/dist/css/tabulator_bootstrap5.min.css" />
<script src="https://cdn.jsdelivr.net/npm/tabulator-tables@6.6.1/dist/js/tabulator.min.js"></script>

<div id="table"></div>

<script>
  new Tabulator("#table", {
    data: [{ id: 1, name: "Alice", role: "Admin" }],
    layout: "fitColumns",
    pagination: true,
    paginationSize: 10,
    columns: [
      { title: "ID", field: "id", width: 60 },
      { title: "Name", field: "name", headerFilter: "input" },
      { title: "Role", field: "role" }
    ]
  })
</script>
```

##### Calendar

###### FullCalendar

[FullCalendar](https://fullcalendar.io/) is the calendar used in `pages/calendar.html`. Drag-and-drop scheduling, multiple views, recurrence, MIT.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@7.1.1/skeleton.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fullcalendar/bootstrap5@7.1.1/theme.css" />
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@7.1.1/all/global.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@fullcalendar/bootstrap5@7.1.1/global.min.js"></script>

<div id="calendar"></div>

<script>
  new FullCalendar.Calendar(document.getElementById("calendar"), {
    initialView: "dayGridMonth",
    headerToolbar: { start: "prev,next today", center: "title", end: "dayGridMonth,timeGridWeek,listWeek" },
    events: [{ title: "Team standup", start: "2026-06-01" }]
  }).render()
</script>
```

##### Maps

###### jsVectorMap

[jsVectorMap](https://github.com/themustafaomar/jsvectormap) is the dependency-free successor to jQuery Mapael / jVectorMap. Choropleth maps, markers, per-region click handlers, and a world map plus per-country data files.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jsvectormap@1.7.0/dist/jsvectormap.min.css" />
<script src="https://cdn.jsdelivr.net/npm/jsvectormap@1.7.0/dist/jsvectormap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsvectormap@1.7.0/dist/maps/world.js"></script>

<div id="map" style="height: 20rem;"></div>

<script>
  new jsVectorMap({
    selector: "#map",
    map: "world",
    markers: [{ name: "Riga", coords: [56.95, 24.11] }]
  })
</script>
```

##### Drag and drop

###### SortableJS

[SortableJS](https://sortablejs.github.io/Sortable/) powers the kanban board demo. Reorder lists, drag between lists, touch-friendly. 31k stars.

```html
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.7/Sortable.min.js"></script>

<ul id="list" class="list-group">
  <li class="list-group-item">Item 1</li>
  <li class="list-group-item">Item 2</li>
  <li class="list-group-item">Item 3</li>
</ul>

<script>
  new Sortable(document.getElementById("list"), { animation: 150 })
</script>
```

##### Lightbox / image viewer

###### GLightbox

[GLightbox](https://biati-digital.github.io/glightbox/) is a vanilla-JS lightbox for images, videos, and inline content.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/glightbox@3.3.1/dist/css/glightbox.min.css" />
<script src="https://cdn.jsdelivr.net/npm/glightbox@3.3.1/dist/js/glightbox.min.js"></script>

<a href="image-large.jpg" class="glightbox">
  <img src="image-thumb.jpg" alt="Click to enlarge" />
</a>

<script>
  GLightbox()
</script>
```

##### Icon libraries

AdminLTE ships [Bootstrap Icons](https://icons.getbootstrap.com/) by default — they're SVG-based, ~1,900 icons, and used throughout the demo. If you need more, these all work alongside AdminLTE:

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

Mix and match — they all coexist.

##### A note on bundling

The examples above use jsDelivr CDNs for copy-paste convenience. For production use, prefer:

1. **npm install + bundler** (Vite, Webpack, Rollup) — best for tree-shaking and version locking
2. **Self-host** the minified bundles — avoids CDN downtime as a failure mode
3. **CDN with `integrity` hashes** — fine for prototypes and demos

AdminLTE doesn't ship any of these libraries — keeping the framework's footprint small is intentional. Pick what you need, skip what you don't.

---

### Src Html Components Docs Introduction (`src/html/components/docs/introduction.mdx`)

## What is AdminLTE?

AdminLTE is a free, MIT-licensed admin dashboard template built on **Bootstrap 5.3.8** with vanilla TypeScript (no jQuery). It ships responsive layouts, a sidebar navigation system, light/dark/auto color mode, RTL support, and a growing catalog of demo pages — calendar, kanban, chat, file manager, invoice, profile, settings, and more.

It's a *template*, not a component library. You drop the markup into your own project (or framework templates) and customize the parts you need. Bootstrap 5 handles the components; AdminLTE provides the application shell.

If you've never used AdminLTE before, start with [Getting Started](getting-started.html) — it walks through a working dashboard in ten minutes.

## Installation paths

Pick the path 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** | You need to customize SCSS variables, sidebar width, breakpoints | 10 minutes |
| **Composer** | PHP projects that prefer Packagist over npm | 2 minutes |

### CDN — fastest path

Drop these four CSS tags into `<head>` and four script tags before `</body>`. No install, no build step:

```html

<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.16.0/styles/overlayscrollbars.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@4.10.1/dist/css/adminlte.min.css" />


<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.16.0/browser/overlayscrollbars.browser.es6.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/admin-lte@4.10.1/dist/js/adminlte.min.js"></script>
```

Then copy any [demo page](https://github.com/ColorlibHQ/AdminLTE/tree/master/dist) HTML into your own file. See [Getting Started](getting-started.html) for the minimum-viable layout.

### npm + bundler

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

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

Then import the CSS and JS in your app 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"
```

Yarn and pnpm work the same way — substitute `yarn add` or `pnpm add` for the install.

### Build from source

If you want to customize SCSS variables (sidebar width, breakpoints, brand colours), clone the repo and build locally:

```bash
git clone https://github.com/ColorlibHQ/AdminLTE.git
cd AdminLTE
npm install
npm run dev          # dev server with hot-reload at 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-overrides.scss`, then rerun the build. See [Customization &amp; Theming](customization.html) for which variables matter.

### Composer

For PHP projects:

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

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

## Prerequisites

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

AdminLTE 4 does **not** support Internet Explorer. If you need IE11, stay on [AdminLTE 3](https://github.com/ColorlibHQ/AdminLTE/releases/tag/v3.2.0) or earlier.

## Where to next

- **[Getting Started](getting-started.html)** — a working dashboard in 10 minutes
- **[Layout](layout.html)** — the app-wrapper structure and responsive options
- **[Customization](customization.html)** — change colours, sidebar width, spacing
- **[Migration from v3](migration.html)** — upgrading an existing AdminLTE 3 project

## Releases and changelog

Always grab the latest from the [GitHub releases page](https://github.com/ColorlibHQ/AdminLTE/releases) — that's where the changelog lives.

Legacy releases:
[AdminLTE 3.2](https://github.com/ColorlibHQ/AdminLTE/releases/tag/v3.2.0) ·
[AdminLTE 2](https://github.com/ColorlibHQ/AdminLTE/releases/tag/v2.4.18) ·
[AdminLTE 1](https://github.com/ColorlibHQ/AdminLTE/releases/tag/1.3.1)

---

### Src Html Components Docs Layout Blueprint (`src/html/components/docs/layout-blueprint.mdx`)

Every AdminLTE page follows the same structural blueprint: a wrapper that contains a header, a sidebar, a main content area, and (optionally) a footer. Understanding the four region classes is the difference between fighting the framework and using it productively.

##### The four regions

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

The wrapper is a CSS grid container, the four children are grid areas. You don't have to know that to use it — the markup pattern alone is sufficient.

##### Minimal markup

```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>
```

Order inside `.app-wrapper` doesn't matter — the grid template positions each child by its class. Use whatever order reads cleanly.

##### Region reference

###### `.app-wrapper`

The root container. Defines the grid template that positions header, sidebar, main, and footer. Always lives directly inside `<body>`.

You can 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 visual. The header is where the sidebar toggle button lives — wire it with `data-lte-toggle="sidebar"`:

```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. 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">
        
      </ul>
    </nav>
  </div>
</aside>
```

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

###### `.app-main`

The page content region. Almost always contains two children:

```html
<main class="app-main">
  <div class="app-content-header">
    
  </div>
  <div class="app-content">
    
  </div>
</main>
```

`.app-content-header` is the title strip — typically an `<h3>` and a breadcrumb in a row. `.app-content` is everything below it. Both should wrap their children 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 to the bottom of the viewport.

##### Body-level layout modifiers

Layout behaviour is mostly controlled by classes on `<body>`, not `.app-wrapper`. The supported modifiers:

| Class | What it does |
|---|---|
| `layout-fixed` | Sidebar gets its own scrollbar; only `.app-main` scrolls with the page |
| `fixed-header` | Sticky `.app-header` at the top; sidebar also pins (since v4.0.0) |
| `fixed-footer` | Sticky `.app-footer` at the bottom |
| `sidebar-expand-{sm,md,lg,xl,xxl}` | Breakpoint at which the sidebar transitions from off-canvas (mobile) to inline (desktop). `lg` is the default in the demos. |
| `sidebar-mini` | Collapses sidebar to 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 behaviour for the mini sidebar |
| `layout-rtl` | Mirrors the layout for right-to-left languages (also requires `.rtl.css` and `dir="rtl"` — see [RTL Support](rtl.html)) |

These compose. The demo dashboards use `class="layout-fixed sidebar-expand-lg bg-body-tertiary"` — fixed-height with a desktop sidebar breakpoint at the `lg` (≥992px) boundary.

###### Layout variants demo

The Layout section of the demo site (`/layout/*.html` files) ships eleven pre-built combinations:

- [Fixed Sidebar](../layout/fixed-sidebar.html), [Fixed Header](../layout/fixed-header.html), [Fixed Footer](../layout/fixed-footer.html), [Fixed Complete](../layout/fixed-complete.html)
- [Unfixed Sidebar](../layout/unfixed-sidebar.html), [Sidebar Mini](../layout/sidebar-mini.html), [Collapsed Sidebar](../layout/collapsed-sidebar.html), [Collapsed without hover](../layout/collapsed-sidebar-without-hover.html)
- [Layout Custom Area](../layout/layout-custom-area.html), [Logo Switch](../layout/logo-switch.html), [Layout RTL](../layout/layout-rtl.html)

##### Responsive behaviour

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 by default and slid in from the left when the user clicks the hamburger toggle.

Mobile-specific classes:

| Class | When applied | Effect |
|---|---|---|
| `sidebar-open` | Body — when the user explicitly opens the mobile sidebar | Slides the sidebar in over the content with an overlay |
| `sidebar-collapse` | Body — set automatically when the user closes the mobile sidebar | Hides the sidebar |

These are managed by the [PushMenu plugin](javascript/pushmenu.html) — you don't write them yourself.

##### Common pitfalls

- **Forgetting `.container-fluid` inside `.app-content`** — the content lands flush against the screen edge. Wrap your content cards in a container.
- **Putting the sidebar toggle outside `.app-header`** — it'll still work, but the styling expects the navbar context.
- **Mixing `.app-*` and `.main-*` classes** — `.main-*` is the AdminLTE 3 naming. Don't combine them. See [Migration from v3](migration.html).
- **Using `position: fixed` on something inside `.app-main`** — `layout-fixed` already gives `.app-main` `overflow: auto`. Children with `position: fixed` may behave unexpectedly. Use sticky positioning instead.
- **Hardcoding sidebar width in custom CSS** — the sidebar width is exposed via the SCSS variable `$lte-sidebar-width`. If you need to know it from JavaScript, read the rendered `.app-sidebar` element's computed width.

##### Where to next

- [Layout reference](layout.html) — the lower-level CSS classes and what they do
- [PushMenu plugin](javascript/pushmenu.html) — the sidebar toggle implementation
- [Customization](customization.html) — how to change sidebar width, breakpoint, and colours

---

### Src Html Components Docs License (`src/html/components/docs/license.mdx`)

AdminLTE is an open source project that is licensed under the [MIT license](https://opensource.org/licenses/MIT). This allows you to do pretty much anything you want as long as you include the copyright in "all copies or substantial portions of the Software." Attribution is not required (though very much appreciated).

<h5 class="text-bold text-dark mt-3">What You Are <span class="text-success">Allowed</span> To Do With AdminLTE</h5>

- Use in commercial projects.
- Use in personal/private projects.
- Modify and change the work.
- Distribute the code.
- Sublicense: incorporate the work into something that has a more restrictive license.

---

### Src Html Components Docs Migration (`src/html/components/docs/migration.mdx`)

AdminLTE 4 is a ground-up rewrite. It targets Bootstrap 5.3, drops jQuery, ships as TypeScript-authored vanilla JS, and reorganises the CSS class structure. If you're upgrading an AdminLTE 3 project, the changes below are the ones most likely to bite.

##### The high-level summary

| | AdminLTE 3 | AdminLTE 4 |
|---|---|---|
| CSS framework | Bootstrap 4.6 | **Bootstrap 5.3** |
| JavaScript | jQuery + vanilla | **Vanilla TypeScript only** |
| Dark mode | Manual `.dark-mode` class | **`data-bs-theme` attribute (Bootstrap-native)** |
| RTL | Separate `rtl.css` build | **Generated automatically via rtlcss** |
| Browser support | IE 11 forks available | Modern evergreen browsers only |
| Build tooling | Gulp | **Rollup + Sass + PostCSS** |
| Icon library | FontAwesome | **Bootstrap Icons** (Lucide / Tabler Icons documented as alternatives) |
| Form widgets | ~25 jQuery plugins bundled in `plugins/` | **None bundled** — vanilla-JS equivalents [documented](#third-party-plugin-replacements) instead |

If your project relies on jQuery or IE 11, stay on AdminLTE 3 — those constraints are not reversible in v4.

##### Class renames

The most common find-and-replace 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` |
| `.brand-link` (inside `.main-sidebar`) | `.sidebar-brand > .brand-link` |
| `data-widget="pushmenu"` | `data-lte-toggle="sidebar"` |
| `data-widget="treeview"` | `data-lte-toggle="treeview"` (on the parent menu) |
| `data-widget="card-widget"` | `data-lte-toggle="card-collapse"`, `card-remove`, `card-maximize` |
| `data-widget="fullscreen"` | `data-lte-toggle="fullscreen"` |
| `data-widget="control-sidebar"` | (removed — Bootstrap offcanvas replaces this) |
| `.dark-mode` (on `<body>`) | `data-bs-theme="dark"` (on any element) |

##### Bootstrap 4 → 5 utility renames

These come from Bootstrap itself, not AdminLTE, but they affect every page:

| Bootstrap 4 | Bootstrap 5 |
|---|---|
| `data-toggle` | `data-bs-toggle` |
| `data-target` | `data-bs-target` |
| `data-dismiss` | `data-bs-dismiss` |
| `data-parent` | `data-bs-parent` |
| `data-spy` | `data-bs-spy` |
| `data-ride` | `data-bs-ride` |
| `.ml-*`, `.mr-*` | `.ms-*`, `.me-*` |
| `.pl-*`, `.pr-*` | `.ps-*`, `.pe-*` |
| `.float-left`, `.float-right` | `.float-start`, `.float-end` |
| `.text-left`, `.text-right` | `.text-start`, `.text-end` |
| `.border-left`, `.border-right` | `.border-start`, `.border-end` |
| `.rounded-left`, `.rounded-right` | `.rounded-start`, `.rounded-end` |
| `.font-weight-*` | `.fw-*` |
| `.font-italic` | `.fst-italic` |
| `.text-monospace` | `.font-monospace` |

A regex find-replace handles most of this in a few minutes. The migration tool [`bootstrap-5-migration`](https://github.com/dbtek/bootstrap-5-migration) automates the bulk.

##### jQuery removal

If your app code calls AdminLTE 3 plugins via jQuery (`$('.sidebar').PushMenu()`, etc.), those calls no longer work. Replacements:

```js
// AdminLTE 3
$('.sidebar-toggle').on('click', function () {
  $('body').toggleClass('sidebar-collapse')
})
```

```js
// AdminLTE 4 — the data-API does this for you
// <a href="#" data-lte-toggle="sidebar">...</a>
```

If you need programmatic control, import the plugin class directly:

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

new PushMenu(document.querySelector(".sidebar-toggle")).toggle()
```

All seven JS plugins (`Layout`, `CardWidget`, `Treeview`, `DirectChat`, `FullScreen`, `PushMenu`, plus `initAccessibility`) are exported from `admin-lte`.

##### Third-party plugin replacements

AdminLTE 3 bundled a `plugins/` folder full of jQuery widgets. AdminLTE 4 ships none of them — it stays framework-only and documents best-in-class vanilla-JS equivalents instead. Every replacement below is MIT-licensed, actively maintained, and needs no jQuery:

| AdminLTE 3 plugin (jQuery) | AdminLTE 4 replacement | Notes |
|---|---|---|
| [Select2](https://select2.org/) | **[Tom Select](https://tom-select.js.org/)** | Search, multi-select, tagging, remote data, virtual scrolling. See the [API mapping](../forms/advanced.html#select2) and the [Select2 compatibility theme](integrations.html#coming-from-select2) if you must keep Select2. |
| [Bootstrap4 Duallistbox](https://www.virtuosoft.eu/code/bootstrap-duallistbox/) | **[Tom Select](https://tom-select.js.org/)** | A `multiple` Tom Select covers the same job with less UI. |
| [DataTables](https://datatables.net/) | **[Tabulator](https://tabulator.info/)** | Sorting, filtering, pagination, inline edit, export. Used in `tables/data.html`. |
| [bootstrap-datepicker](https://github.com/uxsolutions/bootstrap-datepicker) | **[Flatpickr](https://flatpickr.js.org/)** | One library replaces all four v3 date/time plugins. |
| [daterangepicker](https://www.daterangepicker.com/) | **[Flatpickr](https://flatpickr.js.org/)** (`mode: "range"`) | |
| [Bootstrap Timepicker](https://github.com/jdewit/bootstrap-timepicker) | **[Flatpickr](https://flatpickr.js.org/)** (`noCalendar: true`) | |
| [tempusdominus](https://getdatepicker.com/) | **[Flatpickr](https://flatpickr.js.org/)** | |
| [Summernote](https://summernote.org/) | **[Quill](https://quilljs.com/)** | Snow + bubble themes demoed in `forms/editors.html`. |
| [bootstrap4-wysihtml5](https://github.com/bootstrap-wysiwyg/bootstrap3-wysihtml5-bower) | **[Quill](https://quilljs.com/)** | |
| [SimpleMDE](https://simplemde.com/) | **[EasyMDE](https://github.com/Ionaru/easy-markdown-editor)** | EasyMDE *is* the maintained SimpleMDE fork — same API. |
| [bootstrap-slider](https://github.com/seiyria/bootstrap-slider) | **[noUiSlider](https://refreshless.com/nouislider/)** | |
| [iCheck / icheck-bootstrap](https://github.com/bantikyan/icheck-bootstrap) | **Native `.form-check`** | Bootstrap 5.3's own checkboxes and switches need no plugin. |
| [bootstrap-colorpicker](https://itsjavi.com/bootstrap-colorpicker/) | **[Pickr](https://github.com/Simonwep/pickr)** | Or the native `<input type="color" class="form-control form-control-color">`. |
| [jQuery InputMask](https://github.com/RobinHerbots/Inputmask) | **[IMask](https://imask.js.org/)** | |
| [jQuery Knob](https://github.com/aterrien/jQuery-Knob) | **[Chart.js 4](https://www.chartjs.org/)** doughnut | A doughnut with `circumference`/`cutout` and a centre label — see the radial bar on `charts/chartjs.html`. |
| [jQuery Mapael](https://www.vincentbroute.fr/mapael/) | **[jsVectorMap](https://github.com/themustafaomar/jsvectormap)** | |
| [jQuery UI sortable](https://jqueryui.com/sortable/) | **[SortableJS](https://sortablejs.github.io/Sortable/)** | Powers the kanban demo. |
| [fullCalendar](https://fullcalendar.io/) (jQuery build) | **[FullCalendar 7](https://fullcalendar.io/)** | Same project — v5+ dropped jQuery. Used in `pages/calendar.html`. |
| [Chart.js 2](https://www.chartjs.org/) | **[Chart.js 4](https://www.chartjs.org/)** | Chart.js was never jQuery-based; just a major bump. Used in the dashboards and `charts/chartjs.html`. |
| [dropzone](https://www.dropzone.dev/) | **[Dropzone 6](https://www.dropzone.dev/)** or **[FilePond](https://pqina.nl/filepond/)** | v6 is a plain-JS rewrite. |
| [Ekko Lightbox](https://github.com/ashleydw/lightbox) | **[GLightbox](https://biati-digital.github.io/glightbox/)** | |
| [jQuery Overlay Scrollbars](https://kingsora.github.io/OverlayScrollbars/) | **Native `scrollbar-*` CSS** | AdminLTE 4 styles the sidebar scrollbar in CSS. |
| [toastr](https://github.com/CodeSeven/toastr) / [SweetAlert2](https://sweetalert2.github.io/) | **Bootstrap [toasts](https://getbootstrap.com/docs/5.3/components/toasts/)** / **[SweetAlert2](https://sweetalert2.github.io/)** | SweetAlert2 has been jQuery-free since v7. |
| [jsGrid](https://js-grid.com/) | **[Tabulator](https://tabulator.info/)** | |

Install snippets and wiring code for each replacement live on the [Recommended Integrations](integrations.html) page.

##### Dark mode

AdminLTE 3 used a `.dark-mode` class toggled on the `<body>`. AdminLTE 4 uses Bootstrap 5.3's native `data-bs-theme="dark"` attribute, which can be applied at any level — `<html>`, `<body>`, or a single component.

```html

<body class="dark-mode">


<html data-bs-theme="dark">
```

The included [Color Mode](color-mode.html) widget reads/writes this attribute and persists the user's choice in `localStorage`. See the [Color Mode](color-mode.html) docs page for the toggle implementation.

##### Extra colours moved to an opt-in stylesheet

AdminLTE 3 shipped a lot of one-off colour classes (`.bg-navy`, `.bg-purple`, `.bg-fuchsia`, `.bg-gradient-*`, the sidebar/navbar skins) inside its main CSS. In v4 the core stylesheet only knows Bootstrap's theme colours; the extended palette is a separate, opt-in file, `dist/css/adminlte-colors.css`, with a redesigned set of fourteen colours (every one takes white text at ≥ 4.5:1) and the same class families for each — `.bg-*`, `.text-bg-*`, `.bg-gradient-*`, `.card-*`, `.callout-*`, ….

```html

<div class="card bg-navy">…</div>
<aside class="main-sidebar sidebar-dark-primary">…</aside>


<div class="card text-bg-navy">…</div>
<aside class="app-sidebar text-bg-navy shadow" data-bs-theme="dark">…</aside>
```

`lightblue` became `sky` and `maroon` became `pink`; `$lte-palette-aliases` restores the old names if your markup depends on them. Everything else — the palette, skins, adding your brand colours — is on the [Colors](colors.html) page. For retheming Bootstrap's own colours, override `--bs-primary` etc. on `:root`; see [Customization](customization.html).

##### Icons

AdminLTE 3 demos used FontAwesome 6 free. AdminLTE 4 standardises on [Bootstrap Icons](https://icons.getbootstrap.com/) — they're SVG, lighter, MIT-licensed, and ship with Bootstrap's ecosystem.

```html

<i class="fas fa-home"></i>


<i class="bi bi-house"></i>
```

If you'd rather stick with FontAwesome (or use Lucide, Tabler Icons, Material Symbols, etc.), nothing in AdminLTE 4 forces Bootstrap Icons — load whichever icon font you prefer and replace the `<i>` classes. See [Recommended Integrations](integrations.html#icon-libraries) for the comparison table.

##### Pages we haven't ported yet

The v3 demo includes pages that haven't all been recreated for v4. The v4 release notes cover the current page catalog. If you depend on a specific v3 page that hasn't been ported, please [open an issue](https://github.com/ColorlibHQ/AdminLTE/issues) — community contributions for missing pages are very welcome.

##### Suggested migration order

For an existing AdminLTE 3 project:

1. **Branch first.** This is not a small change — keep `master` on v3 until v4 is stable.
2. **Update CSS class names** with a find-replace pass using the table above. Most layouts compile but look broken until step 3.
3. **Update `data-toggle` → `data-bs-toggle`** etc. across all your templates.
4. **Replace `.dark-mode` toggle code** with `data-bs-theme` attribute writes.
5. **Audit jQuery calls.** Either remove them (use the data API) or import plugin classes directly.
6. **Swap the third-party plugins** using the [replacement table](#third-party-plugin-replacements) above. Do this one widget at a time — it's the longest step, and each swap is independently testable.
7. **Test responsive breakpoints** — Bootstrap 5 added `xxl`. If you use breakpoint-specific utilities, double-check the new scale.
8. **Re-test print views** — as in v3, printing leaves out the header, sidebar and footer and gives the content the full sheet; `data-lte-print="plain"` drops the printed link targets and button outlines for pages that print as documents, and `data-lte-print="app"` prints the whole layout instead. If you had custom print CSS, verify it still works.

##### Help and discussion

- Migration questions: [GitHub Discussions](https://github.com/ColorlibHQ/AdminLTE/discussions)
- Found something we missed? [Open an issue](https://github.com/ColorlibHQ/AdminLTE/issues) tagged `migration`.

---

### Src Html Components Docs Recipes (`src/html/components/docs/recipes.mdx`)

Copy-paste solutions for the patterns that show up in every admin dashboard. Each recipe assumes you've followed [Getting Started](getting-started.html) and have a working AdminLTE page to build on.

##### A multi-level sidebar with icons, badges, and headers

The demo dashboards use this exact pattern. Group sections with `.nav-header`, indicate item depth with nested `.nav-treeview` lists, and use `.navbar-badge` for counts.

```
/* Detailed source-code truncated for AI context efficiency. */
```

To mark the current page as active, server-render the `.active` class onto the matching `.nav-link`. For SPA frameworks, add it on route change.

##### Highlighting the active sidebar item from JavaScript

If you're rendering the sidebar once and want client-side route highlighting:

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

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

##### Responsive data tables

Wrap a `<table>` in `.table-responsive` so it gets a horizontal scrollbar on narrow viewports 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>
            <th class="text-end">Actions</th>
          </tr>
        </thead>
        <tbody>
          
        </tbody>
      </table>
    </div>
  </div>
</div>
```

For interactive sorting/filtering/pagination, use [Tabulator](integrations.html#tabulator) — `tables/data.html` in the demo has a full example.

##### A user dropdown in the topbar

```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>
```

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

##### A language switcher wired to a real locale

The demo topbar ships a language dropdown, but the markup alone changes nothing — swapping the locale is the application's job, and doing it badly is worse than not shipping it. The pattern below covers what a switcher actually has to get right.

```html
<li class="nav-item dropdown">
  <a class="nav-link" href="#" id="language-menu" data-bs-toggle="dropdown"
     aria-expanded="false" aria-label="Change language, current language English">
    <i class="bi bi-translate" aria-hidden="true"></i>
  </a>
  <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="language-menu">
    <li>
      <a class="dropdown-item active" href="/en/dashboard" hreflang="en" aria-current="true">
        English <i class="bi bi-check-lg ms-2" aria-hidden="true"></i>
      </a>
    </li>
    <li><a class="dropdown-item" href="/es/dashboard" hreflang="es">Español</a></li>
    <li><a class="dropdown-item" href="/ar/dashboard" hreflang="ar">العربية</a></li>
  </ul>
</li>
```

Four things make the difference between a working switcher and set dressing:

1. **Each item is a real link to the same page in another locale**, not a `#` with a click handler. A link is bookmarkable, crawlable, opens in a new tab, and works before your JavaScript loads. Add `hreflang` so assistive tech announces the language change and search engines can pair the URLs.
2. **The locale belongs in the URL** (`/es/dashboard`) or in a cookie the server reads — not in `localStorage`. Server-rendered HTML has to know the locale *before* it renders, and `localStorage` is only readable after the page has already been sent in the wrong language.
3. **Mark the current language** with both `.active` and `aria-current="true"`, and name it in the toggle's `aria-label`. A flag icon alone is not a language: several languages share a flag, and several countries share a language.
4. **Switching to an RTL locale needs the RTL stylesheet**, not just a `dir` attribute. Serve `adminlte.rtl.css` and set `<html lang="ar" dir="rtl">` together — see [RTL Support](rtl.html).

If your locale lives in the URL, the server picks the stylesheet and the switcher stays pure markup:

```html

<html lang="ar" dir="rtl">
  <link rel="stylesheet" href="/css/adminlte.rtl.css">
```

For a client-rendered app, do the same work at the router level — change the locale, re-render, and swap the stylesheet `href` — rather than translating text nodes in place.

##### A KPI card (small-box)

The bright-coloured stat boxes at the top of most dashboards:

```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>
  
</div>
```

Swap `text-bg-primary` for `text-bg-success`, `text-bg-warning`, `text-bg-danger`, etc.

##### A breadcrumb + page-title row

This is the standard `.app-content-header` pattern used across every demo page:

```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"><a href="/section">Section</a></li>
          <li class="breadcrumb-item active" aria-current="page">Current page</li>
        </ol>
      </div>
    </div>
  </div>
</div>
```

##### A timeline

For activity feeds, audit logs, or roadmap views:

```html
<div class="timeline">
  <div>
    <i class="bi bi-envelope bg-info" aria-hidden="true"></i>
    <div class="timeline-item">
      <span class="time"><i class="bi bi-clock"></i> 12:05</span>
      <h3 class="timeline-header">
        <a href="#">Olivia</a> sent you a message
      </h3>
      <div class="timeline-body">
        Hey, can we review the Q4 numbers tomorrow at 10?
      </div>
      <div class="timeline-footer">
        <a class="btn btn-primary btn-sm">Read more</a>
      </div>
    </div>
  </div>
  
</div>
```

The timeline component lives in `_timeline.scss` and has variants. See [`/UI/timeline.html`](../UI/timeline.html) for the full set.

##### A direct-chat widget

The chat-style card used for live customer support, team chat, etc. Includes contact toggle and message bubbles.

```
/* Detailed source-code truncated for AI context efficiency. */
```

For a full-page chat application, see `/pages/chat.html` in the demo.

##### Reacting to layout/sidebar changes

Listen for plugin events to coordinate with your own code — e.g., refit a widget when the sidebar collapses (so it re-measures its container width):

```js
let table = /* your Tabulator instance */

document.addEventListener("collapse.lte.push-menu", () => {
  // wait for the transition to finish (~300ms by default)
  setTimeout(() => table.redraw(), 350)
})

document.addEventListener("open.lte.push-menu", () => {
  setTimeout(() => table.redraw(), 350)
})
```

The same pattern works for FullCalendar (`calendar.updateSize()`) and any other library that reads its container width on init. Chart.js charts need nothing here: they watch their container with a `ResizeObserver` and redraw on their own.

##### Loading content into a card via fetch

```html
<div class="card" id="users-card">
  <div class="card-header">
    <h3 class="card-title">Users</h3>
    <div class="card-tools">
      <span class="spinner-border spinner-border-sm text-secondary d-none" id="users-spinner" role="status"></span>
    </div>
  </div>
  <div class="card-body p-0">
    <div id="users-body" class="text-secondary p-3">Loading&hellip;</div>
  </div>
</div>

<script>
  const spinner = document.getElementById("users-spinner")
  const body = document.getElementById("users-body")

  spinner.classList.remove("d-none")
  fetch("/api/users")
    .then((r) => r.text())
    .then((html) => { body.innerHTML = html })
    .catch(() => { body.textContent = "Failed to load." })
    .finally(() => spinner.classList.add("d-none"))
</script>
```

##### Confirming destructive actions before submission

Bootstrap modal + a tiny confirmation pattern:

```html
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#confirmDelete">
  Delete account
</button>

<div class="modal fade" id="confirmDelete" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Delete account?</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        This permanently deletes your account and all associated data. This cannot be undone.
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
        <form action="/account/delete" method="POST" class="d-inline">
          <button type="submit" class="btn btn-danger">Yes, delete it</button>
        </form>
      </div>
    </div>
  </div>
</div>
```

The [Color Mode](color-mode.html), [Accessibility](javascript/accessibility.html), and the bundled JS handle Escape-to-close, focus trap, and keyboard navigation inside the modal automatically.

##### Charts that follow dark mode

Charting libraries draw on a canvas, so they know nothing about `data-bs-theme` or any other CSS: Chart.js paints its axis labels, gridlines and tooltip in its own fixed default colours, which suit a light page and all but vanish on a dark card. You have to hand it the colours, and hand them over again when the mode changes.

The demo pages that draw charts ship a small preset for this (`src/html/components/_chartjs.astro`, inlined after Chart.js on each of those pages). It reads AdminLTE's CSS variables into `Chart.defaults` — font family, text colour, gridlines, a tooltip and legend styled like the rest of the UI, `rtl` when `<html dir="rtl">` — and watches `<html>` so every chart on the page is restyled when the mode changes, whether that comes from the toggle, the OS preference in auto mode, or your own code. The core of it:

```html
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>
<script>
  const root = document.documentElement
  const cssVar = (name) => getComputedStyle(root).getPropertyValue(name).trim()

  const applyTheme = () => {
    const grid = `rgba(${cssVar("--bs-emphasis-color-rgb")}, 0.08)`
    Chart.defaults.font.family = cssVar("--bs-body-font-family")
    Chart.defaults.color = cssVar("--bs-secondary-color")        // ticks and titles
    Chart.defaults.borderColor = grid                            // gridlines and axis lines
    Chart.defaults.plugins.legend.labels.color = cssVar("--bs-body-color")
    Object.assign(Chart.defaults.plugins.tooltip, {
      backgroundColor: cssVar("--bs-body-bg"),
      borderColor: cssVar("--bs-border-color"),
      borderWidth: 1,
      titleColor: cssVar("--bs-emphasis-color"),
      bodyColor: cssVar("--bs-body-color")
    })
    Chart.defaults.elements.arc.borderColor = cssVar("--bs-body-bg") // doughnut slice gaps
  }

  applyTheme()
  new MutationObserver(() => {
    applyTheme()
    for (const chart of Object.values(Chart.instances)) chart.update("none")
  }).observe(root, { attributes: true, attributeFilter: ["data-bs-theme"] })
</script>
```

Charts created after this script pick the colours up by default, and `chart.update()` makes existing ones re-read them. Colours you set on a dataset (`borderColor: "#0d6efd"`) are yours and stay as they are; a colour that should follow the mode can be a function, which Chart.js calls again on every update — `backgroundColor: () => cssVar("--bs-secondary-bg")`.

If you prefer the ColorMode event to the attribute observer, `document.addEventListener("changed.lte.color-mode", e => …)` receives `e.detail.resolved` (`"light"` or `"dark"`); the observer above additionally covers OS-preference changes in auto mode and any code that sets the attribute directly. The same idea applies to other chart libraries: find where they take their text, grid and tooltip colours, feed them the CSS variables, and re-apply on change.

##### Printing a page as a document

A printed page is a document, not a screenshot of the app, so AdminLTE leaves the shell — header, sidebar menu and footer — off the paper and gives the content the full width of the sheet. What does print is styled for reading on paper: the target of an external link is printed after it, buttons keep an outline so they still read as buttons, and everything goes black on white so backgrounds don't eat ink.

For pages that print as *documents* — an invoice, quote, work order or packing slip that goes to a customer — those last two are the wrong output: nobody wants `Pay online (https://pay.example.com/inv/12345?token=…)` on a printed invoice, or a box drawn around a button. Put `data-lte-print="plain"` on `<html>`, `<body>` or any container and both go away, while the text stays black on white:

```html
<html lang="en" data-lte-print="plain">
```

Scope it to part of a page when only that part is the document:

```html
<div class="card" data-lte-print="plain">…the invoice…</div>
```

And if a page only ever prints as a document, set the attribute when you print:

```js
document.querySelector("#print-invoice").addEventListener("click", () => {
  document.documentElement.setAttribute("data-lte-print", "plain")
  window.print()
})
```

Going the other way — a page that really does print as a copy of the screen, menu and all — `data-lte-print="app"` on `<html>` or `<body>` prints the whole shell:

```html
<html lang="en" data-lte-print="app">
```

To bring back one part of it rather than all three, use Bootstrap's own display utilities. `.d-print-flex` suits the header (it is a navbar), `.d-print-block` the sidebar and footer:

```html
<aside class="app-sidebar bg-body-secondary shadow d-print-block" data-bs-theme="dark">…</aside>
```

`.d-print-none` still hides anything you don't want on paper, in either mode — a toolbar of buttons above the invoice, say.

##### Where to next

- [JavaScript Plugins Overview](javascript/plugins-overview.html) — every plugin's event names and methods
- [Recommended Integrations](integrations.html) — when you need more than what's in this list
- [Customization](customization.html) — change colours, sidebar width, breakpoints

---

### Src Html Components Docs Rtl (`src/html/components/docs/rtl.mdx`)

AdminLTE ships an RTL (right-to-left) stylesheet alongside the standard LTR one, generated from the same source via [`rtlcss`](https://rtlcss.com/). Use it for Arabic, Hebrew, Persian, Urdu, and any other right-to-left scripts.

##### The pre-built RTL stylesheet

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
```

Optional stylesheets get the same treatment — the [Select2 compatibility theme](integrations.html#select2-compatibility-theme), for instance, ships as `adminlte-select2.rtl.css` / `.rtl.min.css` alongside its LTR build.

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

```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">
    
  </body>
</html>
```

That's it — the sidebar now docks to the right, scroll bars flip, padding/margin utilities mirror, and Bootstrap Icons stay LTR-safe (most icons aren't directional). For a live example, see [`layout/layout-rtl.html`](../layout/layout-rtl.html).

##### Bootstrap's `dir="rtl"` attribute

Setting `dir="rtl"` triggers Bootstrap 5.3's logical property pathway as well — `.ms-*` becomes right-margin instead of left, `.float-start` floats right, etc. You don't need separate Bootstrap classes for RTL; the same utility names work in both directions.

##### When you have a mixed-language UI

If your app shows RTL content on an otherwise-LTR page (eg. an Arabic text block inside an English admin panel), you can scope `dir="rtl"` to a single element:

```html
<body>
  
  <main dir="rtl" lang="ar">
    
  </main>
</body>
```

Bootstrap and AdminLTE handle nested direction switches correctly — the inner element's direction is what counts.

##### Building RTL yourself

If you're building AdminLTE from source and you've modified the SCSS, regenerate the RTL CSS:

```bash
npm run css-compile     # builds LTR
npm run css-prefix      # adds autoprefixer
npm run css-rtl         # generates *.rtl.css from the LTR output
npm run css-minify
```

The full pipeline is wired into `npm run css`, so most of the time you just run that.

The conversion is handled by [`postcss`](https://postcss.org/) running [`rtlcss`](https://rtlcss.com/) — both are already in devDependencies. If you need to opt-out a specific rule from the LTR-to-RTL flip, use rtlcss control directives in your SCSS:

```scss
.brand-link {
  /*rtl:ignore*/
  margin-left: .5rem;   // stays left-margin in both LTR and RTL
}
```

##### What flips automatically

| Direction-aware property | Behaviour in RTL |
|---|---|
| `margin-left` / `padding-left` | Swapped to right |
| `left`, `right` positioning | Swapped |
| `text-align: left` / `right` | Swapped |
| Bootstrap `.ms-*`, `.me-*`, `.float-start`, `.float-end` | Mirror via Bootstrap's logical properties |
| `transform: translateX()` | Sign inverted |
| Sidebar position | Docks to the right |
| Dropdown menu open direction | Flips automatically (Bootstrap handles this) |

##### What doesn't flip

- Bootstrap Icons (SVG glyphs) — most icons are direction-neutral; arrows that *do* indicate direction (`bi-arrow-right`, `bi-chevron-right`, etc.) need to be manually swapped to their `-left` siblings.
- Inline images, charts, and third-party widgets — their internal layout doesn't react to `dir`. Check each integration (Chart.js, FullCalendar, Tabulator) for an RTL option. The demo's Chart.js preset sets `rtl: true` on legends and tooltips when `<html dir="rtl">`.
- Numbers — digits stay LTR even in RTL contexts (this is standard Unicode bidi behaviour, not a bug).

##### Common gotchas

- **The `.min.css` minified RTL build is `.rtl.min.css`, not `.min.rtl.css`.** Filename order matters; the build script emits the former.
- **Don't mix LTR and RTL stylesheets on the same page** — pick one. If you need a mixed-direction app, use `dir="rtl"` on the relevant subtree, not a separate stylesheet.
- **Icon mirroring**: when you swap an icon for RTL, also update its `aria-label` if it indicates direction (eg. "Next page" should still say "Next page" — only the glyph rotates).

---

### Src Html Components Docs Components Card (`src/html/components/docs/components/card.mdx`)

Cards are the main content container in AdminLTE. They are [Bootstrap cards](https://getbootstrap.com/docs/5.3/components/card/) with an added shadow, a colour-variant system, a header toolbar, and hooks for the [CardWidget plugin](../javascript/card-widget.html) that collapses, removes and maximises them.

##### Anatomy

```html
<div class="card card-primary card-outline">
  <div class="card-header">
    <div class="card-title">Title</div>
    <div class="card-tools">
      <button type="button" class="btn btn-tool" data-lte-toggle="card-collapse" aria-label="Collapse card">
        <i data-lte-icon="expand" class="bi bi-plus-lg"></i>
        <i data-lte-icon="collapse" class="bi bi-dash-lg"></i>
      </button>
    </div>
  </div>
  <div class="card-body">Body</div>
  <div class="card-footer">Footer</div>
</div>
```

| Class | Description |
|---|---|
| `.card` | The container. Adds AdminLTE's shadow on top of the Bootstrap card. |
| `.card-header` | Header strip. Positioned relative, so `.card-tools` can float inside it. |
| `.card-title` | Header text. Floats left at `1.1rem`; it is a style, not a heading — use whatever element the document outline calls for. |
| `.card-tools` | Right-aligned toolbar inside the header. Absorbs the header's horizontal padding so buttons sit flush. |
| `.btn-tool` | Muted, borderless icon button for use inside `.card-tools`. Add `.btn-tool-custom` to keep your own colours. |
| `.card-body` | Content area. |
| `.card-footer` | Footer strip. |

##### Colour variants

Every entry in `$theme-colors` produces a `.card-*` class. The variant sets two custom properties — `--lte-card-variant-bg` and `--lte-card-variant-color` — which the header then consumes, so `.bg-primary` and `.text-bg-primary` colour a card the same way `.card-primary` does.

```html
<div class="card card-primary">…</div>
<div class="card card-success">…</div>
<div class="card card-danger">…</div>
```

Add `.card-outline` to move the colour to a 3px top border and leave the header plain:

```html
<div class="card card-primary card-outline">…</div>
```

##### States

These classes are the resting state that [CardWidget](../javascript/card-widget.html) toggles. Setting one in your markup is how you render a card that starts out collapsed.

| Class | Description |
|---|---|
| `.collapsed-card` | Hides the body and footer, and swaps the collapse icon for the expand icon. |
| `.maximized-card` | Fixes the card to the viewport at full size. AdminLTE also sets it on `<html>` to lock page scrolling. |
| `.was-collapsed` | Set by CardWidget on a card that was collapsed when it got maximised, so restoring returns it to collapsed. |
| `.expanding-card` | Present for the duration of the expand animation. |

Icon visibility follows the state automatically: `[data-lte-icon="expand"]` is hidden unless the card is collapsed, and `[data-lte-icon="minimize"]` is hidden unless it is maximised. Ship both icons and let the CSS choose.

##### Cards with tabs

`.card-tabs` puts a `.nav-tabs` in the header and squares off the seam between the tabs and the body:

```html
<div class="card card-primary card-tabs">
  <div class="card-header p-0 pt-1">
    <ul class="nav nav-tabs" role="tablist">
      <li class="nav-item" role="presentation">
        <button class="nav-link active" data-bs-toggle="pill" data-bs-target="#tab-1" type="button" role="tab">Tab 1</button>
      </li>
      <li class="nav-item" role="presentation">
        <button class="nav-link" data-bs-toggle="pill" data-bs-target="#tab-2" type="button" role="tab">Tab 2</button>
      </li>
    </ul>
  </div>
  <div class="card-body">
    <div class="tab-content">
      <div class="tab-pane fade show active" id="tab-1" role="tabpanel" tabindex="0">…</div>
      <div class="tab-pane fade" id="tab-2" role="tabpanel" tabindex="0">…</div>
    </div>
  </div>
</div>
```

`.card-outline-tabs` is the same idea for outline cards: the active tab carries the accent as a top border instead of the card doing it.

##### Utilities

| Class | Description |
|---|---|
| `.height-control` | Caps the body at `300px` and scrolls it. |
| `.card .nav.flex-column` | A vertical nav inside a card gets dividers between items and none after the last. Used by the [user widgets](miscellaneous.html#social-and-post-widgets). |

##### Sass variables

| Variable | Default | Description |
|---|---|---|
| `$lte-card-shadow` | two-layer shadow from `--bs-body-color-rgb` | The shadow applied to every `.card`. |
| `$lte-card-title-font-size` | `1.1rem` | `.card-title` size. |
| `$lte-card-title-font-weight` | `$font-weight-normal` | `.card-title` weight. |

##### Notes

- `.card-title` floats, so a `.card-header` containing only a title and tools has no height of its own — the clearfix mixin on the header restores it. Adding your own floated element to a header is safe for the same reason.
- Nested cards are scoped correctly: the collapse and expand rules use `> .card-header` and `> .card-body`, so collapsing an outer card doesn't rewrite the icons of a card inside it.
- For a card that toggles from a button elsewhere on the page, use CardWidget's [programmatic API](../javascript/card-widget.html#programmatic-api) rather than adding and removing the state classes by hand — it fires the matching events.

---

### Src Html Components Docs Components Main Header (`src/html/components/docs/components/main-header.mdx`)

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Src Html Components Docs Components Main Sidebar (`src/html/components/docs/components/main-sidebar.mdx`)

The sidebar used in this page to the left provides an example of what your sidebar would look like. Construction of a sidebar:

```
/* Detailed source-code truncated for AI context efficiency. */
```

## Alternate Logo

You can use two logo images instead of logo with text, you only need to change the markup to this:

```html
<div class="sidebar-brand">
  <a href="./index.html" class="brand-link logo-switch">
    <img
      src="./assets/img/AdminLTELogo.png"
      alt="AdminLTE Logo Small"
      class="brand-image-xl logo-xs opacity-75 shadow"
    />
    <img
      src="./assets/img/AdminLTEFullLogo.png"
      alt="AdminLTE Logo Large"
      class="brand-image-xs logo-xl opacity-75"
    />
  </a>
</div>
```

---

### Src Html Components Docs Components Miscellaneous (`src/html/components/docs/components/miscellaneous.mdx`)

The components on this page are the ones AdminLTE adds on top of Bootstrap that aren't the header, the sidebar or the [card](card.html). Each is CSS-only unless noted.

##### Callouts

A bordered note block, tinted per theme colour. Used throughout these docs.

```html
<div class="callout callout-info">
  <h5 class="fw-bold">Heads up</h5>
  <p>Body text. <a href="#" class="callout-link">A link</a>.</p>
</div>
```

| Class | Description |
|---|---|
| `.callout` | Base block: padding and a `.25rem` left border. |
| `.callout-{color}` | One per `$theme-colors` entry. Sets the background, border and text-emphasis colours together. |
| `.callout-link` | Bolds a link inside a callout. Plain links and `<code>` are already tinted to the variant's emphasis colour, so they stay legible on the subtle background. |

Consecutive callouts collapse the gap between them, so a stack reads as one block.

##### Ribbons

A diagonal banner pinned to the top corner of a positioned ancestor. See the [ribbons demo](../../UI/ribbons.html).

```html
<div class="card">
  <div class="ribbon-wrapper">
    <div class="ribbon text-bg-primary">New</div>
  </div>
  <div class="card-body">…</div>
</div>
```

| Class | Description |
|---|---|
| `.ribbon-wrapper` | The square that clips the banner. Must sit inside something positioned — `.card` already is; anything else needs `.position-relative`. |
| `.ribbon` | The banner itself. Colour comes from a `.text-bg-*` utility. |
| `.ribbon-wrapper.ribbon-lg` | Larger corner, `1rem` text. |
| `.ribbon-wrapper.ribbon-xl` | Largest corner, `1.25rem` text. |

The banner's width is derived from `--lte-ribbon-size`, so overriding that one property rescales the whole thing:

```css
.ribbon-wrapper.ribbon-custom {
  --lte-ribbon-size: 90px;
  --lte-ribbon-width: 130px;
}
```

Keep labels short. The banner is clipped to the corner square, so only about `64px` of it is readable at the default size — roughly six characters. `.ribbon-lg` and `.ribbon-xl` give you more room. In RTL the corner and the rotation both mirror, with no extra markup.

##### Small box

A large stat tile with a background colour, a watermark icon and a footer link.

```html
<div class="small-box text-bg-primary">
  <div class="inner">
    <h3>150</h3>
    <p>New Orders</p>
  </div>
  <svg class="small-box-icon" aria-hidden="true">…</svg>
  <a href="#" class="small-box-footer link-light">More info <i class="bi bi-link-45deg"></i></a>
</div>
```

| Class | Description |
|---|---|
| `.small-box` | The tile. Pair with a `.text-bg-*` utility for colour. |
| `.inner` | Holds the figure and its label. |
| `.small-box-icon` | Oversized watermark icon, anchored to the trailing edge. Mark it `aria-hidden="true"`. |
| `.small-box-footer` | Full-width footer link. |

##### Info box

A compact stat row: icon, label, figure and an optional progress bar.

```html
<div class="info-box">
  <span class="info-box-icon text-bg-primary shadow-sm">
    <i class="bi bi-gear-fill"></i>
  </span>
  <div class="info-box-content">
    <span class="info-box-text">CPU Traffic</span>
    <span class="info-box-number">10 <small>%</small></span>
    <div class="progress">
      <div class="progress-bar" style="width: 70%"></div>
    </div>
    <span class="progress-description">70% increase in 30 days</span>
  </div>
</div>
```

| Class | Description |
|---|---|
| `.info-box` | The row. |
| `.info-box-icon` | Square icon area. Colour it with `.text-bg-*`. |
| `.info-box-content` | Text column. |
| `.info-box-text` | Label. Truncates rather than wrapping. |
| `.info-box-number` | The figure. |
| `.info-box-more` | Optional trailing link. |
| `.progress-description` | Caption under an embedded progress bar. |

##### Timeline

A vertical, dated activity list.

```html
<div class="timeline">
  <div class="time-label"><span class="text-bg-danger">10 Feb. 2026</span></div>
  <div>
    <i class="timeline-icon bi bi-envelope text-bg-primary"></i>
    <div class="timeline-item">
      <span class="time"><i class="bi bi-clock-fill"></i> 12:05</span>
      <h3 class="timeline-header"><a href="#">Support Team</a> sent you an email</h3>
      <div class="timeline-body">…</div>
      <div class="timeline-footer"><a class="btn btn-primary btn-sm">Read more</a></div>
    </div>
  </div>
</div>
```

| Class | Description |
|---|---|
| `.timeline` | The container, drawing the vertical rule. |
| `.time-label` | A date marker interrupting the rule. |
| `.timeline-icon` | The dot on the rule. Colour it with `.text-bg-*`. |
| `.timeline-item` | The entry's panel. |
| `.time` | Trailing timestamp inside the panel. |
| `.timeline-header` / `.timeline-body` / `.timeline-footer` | The panel's three sections. Body and footer are optional. |
| `.timeline-inverse` | Flat variant: no shadow, a plain border instead. |

Each entry is a bare `<div>` wrapping the icon and the item — the wrapper carries no class.

##### Social and post widgets

Profile cards and activity-feed blocks. See the [social widgets demo](../../widgets/social.html). These keep their v3 class names so migrating markup works unchanged, but they are laid out with grid and flex rather than floats, and take their colours from Bootstrap utilities.

```html
<div class="user-block">
  <img class="rounded-circle" src="user.jpg" alt="">
  <span class="username"><a href="#">Jonathan Burke Jr.</a></span>
  <span class="description">Shared publicly · 7:30 PM today</span>
</div>
```

| Class | Description |
|---|---|
| `.user-block` | Avatar, name and meta line from flat markup — the avatar spans both text rows, so no wrapper element is needed. An optional `.comment` lands on a third row. |
| `.user-block.user-block-sm` | Smaller avatar and type, for comment threads. |
| `.post` | One entry in a feed. Divided from the next; the last entry drops its divider and trailing space. |
| `.description-block` | A centred figure with an uppercase caption — `.description-header` and `.description-text`. Repeat across a card footer's columns. |
| `.widget-user` | Profile card with a banner header and an avatar straddling the header/footer seam. Needs `.widget-user-header`, `.widget-user-image`, `.widget-user-username`, `.widget-user-desc`. |
| `.widget-user-2` | Shorter variant with the avatar inline in the header, so long names can wrap. |

For `.widget-user`, the header's height and the avatar's size are Sass variables (`$lte-widget-user-header-height`, `$lte-widget-user-image-size`); the footer's top padding is derived from them, so changing one value keeps the overlap correct.

##### Progress bars

Extra sizes on top of Bootstrap's `.progress`.

| Class | Height |
|---|---|
| `.progress-sm` | `10px` |
| `.progress-xs` | `7px` |
| `.progress-xxs` | `3px` |

`.progress.vertical` turns a bar on its side, and `.progress-group` pairs a bar with a label row.

##### Toasts

`.toast-{color}` themes a Bootstrap toast per `$theme-colors`: coloured header, subtle body, matching border. The close button flips to its white variant on dark headers automatically, in both colour modes.

```html
<div class="toast toast-success" role="alert" aria-live="assertive" aria-atomic="true">
  <div class="toast-header">
    <strong class="me-auto">Saved</strong>
    <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
  </div>
  <div class="toast-body">Your changes have been saved.</div>
</div>
```

##### Image sizes

`.img-size-32`, `.img-size-50` and `.img-size-64` set a width in pixels and leave the height automatic. They exist for avatars in dropdowns and lists, where a Bootstrap sizing utility would need two classes.

##### Direct chat

The chat panel has its own page — see the [DirectChat plugin](../javascript/direct-chat.html) for its markup and the contacts-pane toggle.

---

--- METRICS ---
- Files Extracted: 19
- Estimated Token Budget: ~38593 tokens
- Canonical Reference: https://codewiki.google/github.com/ColorlibHQ/AdminLTE
