# AdminLTE 4 Admin & Architecture Guide

This guide is for the people who **build, customize, deploy, and maintain** AdminLTE: architects integrating it into a platform, front-end engineers building from source, DevOps owners deploying the compiled assets, and contributors hacking on the project itself. It documents the source-tree architecture, the build pipeline, the internals of all seven JavaScript plugins, the SCSS variable system, the Astro-based documentation site, the release/deploy model, security posture, and concrete improvement opportunities.

The companion **User Guide** covers consuming AdminLTE (markup, plugins, theming) from an app developer's seat. This guide goes underneath that — how AdminLTE is *constructed*.

> [!NOTE]
> AdminLTE is a **distributable front-end template**, not a runnable service. It has no server, no database, no API, and no runtime configuration. Its "deployment" is publishing static CSS/JS/asset files to a CDN or web root. Read "architecture" here as *build-time and source-tree architecture*, and "operations" as *build, release, and asset-hosting operations*.

---

## Table of Contents

1. [Executive Summary](#1-executive-summary)
2. [System Architecture](#2-system-architecture)
3. [Repository Structure](#3-repository-structure)
4. [The Build Pipeline](#4-the-build-pipeline)
5. [JavaScript Plugin Internals](#5-javascript-plugin-internals)
6. [The SCSS Architecture](#6-the-scss-architecture)
7. [The Documentation Site (Astro)](#7-the-documentation-site-astro)
8. [Configuration Reference](#8-configuration-reference)
9. [Build & Release Operations](#9-build--release-operations)
10. [Deployment & Performance](#10-deployment--performance)
11. [Security Analysis](#11-security-analysis)
12. [Code Quality & Conventions](#12-code-quality--conventions)
13. [Testing & CI](#13-testing--ci)
14. [Potential Improvements](#14-potential-improvements)
15. [Contributing](#15-contributing)
16. [Quick Reference](#16-quick-reference)

---

## 1. Executive Summary

| Attribute | Value |
|---|---|
| **Project** | AdminLTE — responsive admin dashboard template |
| **Version** | 4.0.0 (first stable release of the v4 line) |
| **License** | MIT |
| **Maintainer** | Colorlib (ColorlibHQ) |
| **Foundation** | Bootstrap 5.3.8, vanilla TypeScript (no jQuery) |
| **Authoring languages** | TypeScript (plugins), SCSS (styles), Astro/MDX (docs site) |
| **Build tooling** | Rollup (JS), Sass + PostCSS (CSS), Astro (docs), npm-run-all orchestration |
| **Distributable** | `dist/css/adminlte*.css` + `dist/js/adminlte*.js` (+ source maps, assets) |
| **Runtime deps (peer)** | Bootstrap, Popper, OverlayScrollbars, Bootstrap Icons |
| **Node baseline** | 18 LTS minimum; 22 LTS in CI |
| **Browser target** | Modern evergreen; no IE 11 |

**What v4 is.** A ground-up rewrite on Bootstrap 5.3 that drops jQuery entirely. The interactive layer is seven small TypeScript classes compiled to a single UMD bundle; the visual layer is a modular SCSS system layered on Bootstrap's CSS-custom-property theming; and the documentation/demo site is an Astro static build. The published artifact is tiny — the JS bundle gzips to roughly **5 KB** and the CSS to roughly **40 KB**.

**Architectural philosophy.** Three principles run through the codebase:
- **Lean core, bring-your-own widgets.** AdminLTE bundles no charts, date pickers, or data-table libraries. It documents recommended third-party libraries instead, keeping its own footprint minimal.
- **Declarative data API over imperative wiring.** Every plugin is driven by `data-lte-*` attributes and self-initializes on `DOMContentLoaded`, so consumers rarely write JavaScript.
- **Defer to Bootstrap where Bootstrap is authoritative.** Color modes use Bootstrap 5.3's `data-bs-theme`; RTL uses logical properties; modal/dropdown focus and Escape handling are left to Bootstrap. AdminLTE adds only the application *shell*.

---

## 2. System Architecture

AdminLTE has two distinct build outputs from one source tree: the **distributable template** (what consumers install) and the **documentation/demo site** (what `adminlte.io` serves).

```text
                          src/  (authored source)
                          │
        ┌─────────────────┼──────────────────────────┬─────────────────┐
        │                 │                          │                 │
   src/ts/*.ts       src/scss/*.scss            src/assets/        src/html/  (Astro)
   (TypeScript)      (SCSS modules)             (images)           pages + components + docs
        │                 │                          │                 │
   Rollup +          Sass → PostCSS              fs-extra copy     Astro static build
   @rollup/plugin-   (autoprefixer,                  │             (reads dist/ via
   typescript        rtlcss) → clean-css             │              copy-assets)
        │                 │                          │                 │
        ▼                 ▼                          ▼                 ▼
  dist/js/           dist/css/                  dist/assets/      dist/html/ → flattened → dist/
  adminlte.js        adminlte.css                                 (demo pages: index, docs, …)
  adminlte.min.js    adminlte.min.css
  (+ .map)           adminlte.rtl.css
                     adminlte.rtl.min.css
                     (+ .map)
```

### Two consumption modes

1. **Prebuilt CSS/JS** — consumers load `dist/css/adminlte.min.css` and `dist/js/adminlte.min.js` (via CDN, npm, or Composer) and never touch the build. This is the dominant mode.
2. **Source build** — consumers who need to change SCSS variables (sidebar width, breakpoints) point their bundler at `src/scss/adminlte.scss` or clone and run the npm build.

### The interactive layer (runtime)

At runtime in the browser, the architecture is event-driven and stateless beyond the DOM:

```text
DOMContentLoaded
   │
   ├─ Layout        → manages body classes (hold-transition, app-loaded), resize handler
   ├─ PushMenu      → reads sidebar data-attrs, sets up overlay, wires toggle buttons, resize handler
   ├─ Treeview      → wires data-lte-toggle="treeview" click delegation
   ├─ CardWidget    → wires card-collapse/remove/maximize buttons
   ├─ DirectChat    → wires chat-pane buttons
   ├─ FullScreen    → wires fullscreen buttons
   └─ initAccessibility() → installs live region, skip links, key handlers, MutationObserver
```

There is no central registry or app object — each module independently queries the DOM and attaches listeners. State lives entirely in CSS classes on `<body>`, `<html>`, `.card`, and `.nav-item`, plus two optional `localStorage` keys (`lte.sidebar.state`, and the docs' `theme` key). This makes the bundle resilient to partial DOMs and framework re-renders, at the cost of having no single source of truth.

---

## 3. Repository Structure

```text
AdminLTE/
├── src/
│   ├── ts/                       # TypeScript plugin source (the JS product)
│   │   ├── adminlte.ts           #   bundle entry point — imports + re-exports all plugins
│   │   ├── layout.ts             #   Layout plugin
│   │   ├── push-menu.ts          #   PushMenu (sidebar) plugin
│   │   ├── treeview.ts           #   Treeview (sidebar submenu) plugin
│   │   ├── card-widget.ts        #   CardWidget plugin
│   │   ├── direct-chat.ts        #   DirectChat plugin
│   │   ├── fullscreen.ts         #   FullScreen plugin
│   │   ├── accessibility.ts      #   AccessibilityManager (WCAG 2.1 AA)
│   │   └── util/index.ts         #   shared utilities (onDOMContentLoaded, slideUp/Down, etc.)
│   ├── scss/                     # SCSS source (the CSS product)
│   │   ├── adminlte.scss         #   master import file
│   │   ├── _variables.scss       #   AdminLTE variables (sidebar, header, transitions)
│   │   ├── _bootstrap-variables.scss   # Bootstrap variable overrides
│   │   ├── _variables-dark.scss  #   dark-mode variable overrides
│   │   ├── _app-*.scss           #   layout regions (wrapper, header, sidebar, main, content, footer)
│   │   ├── _cards.scss, _small-box.scss, _info-box.scss, _timeline.scss, …  # components
│   │   ├── mixins/               #   _animations.scss, _scrollbar.scss
│   │   ├── pages/                #   _lockscreen.scss, _login_and_register.scss
│   │   └── parts/                #   _core, _components, _extra-components, _pages, _miscellaneous
│   ├── config/                   # build configuration
│   │   ├── rollup.config.js      #   JS bundling (UMD, banner, TypeScript plugin)
│   │   ├── astro.config.mjs      #   docs-site build
│   │   ├── postcss.config.mjs    #   autoprefixer + rtlcss
│   │   └── assets.config.mjs     #   copies src/assets → dist/assets
│   ├── html/                     # Astro documentation + demo site
│   │   ├── pages/                #   .astro pages (index, docs/*, layout/*, pages/*, forms/*, …)
│   │   ├── components/           #   .astro layout partials + .mdx doc content
│   │   ├── public/               #   build output staged for Astro (copied from dist/)
│   │   └── env.d.ts, *.tsconfig.json
│   ├── utils/index.js            # tiny helper (convertPathToHtml) used by the docs build
│   └── assets/                   # source images (logos, avatars, demo photos)
├── dist/                         # BUILD OUTPUT — committed to the repo (see §9)
├── package.json                  # scripts + devDependencies (no runtime deps)
├── composer.json                 # Packagist metadata (PHP distribution)
├── tsconfig.json                 # TypeScript compiler options (strict, ES2022, DOM)
├── eslint.config.js              # flat ESLint config (typescript-eslint + unicorn + astro)
├── .stylelintrc.json             # Stylelint (twbs-bootstrap config + overrides)
├── .browserslistrc               # browser targets
├── .bundlewatch.config.json      # per-asset size budgets enforced in CI
├── netlify.toml                  # docs-site deploy config (static publish of dist/)
└── README.md, SECURITY.md, CHANGELOG.md, ACCESSIBILITY-COMPLIANCE.md, LICENSE
```

### Triage of importance

When reasoning about AdminLTE's *behavior*, the high-value source is:

| Folder | Role | Source weight |
|---|---|---|
| `src/ts/` | All interactive behavior — the 7 plugins + utilities | ~49 KB, highest |
| `src/scss/` | All styling — layout grid, components, theming | ~26 SCSS modules |
| `src/config/` | The build wiring | 4 config files |
| `src/html/` | Docs + demo pages (Astro/MDX) — also the project's own authored documentation | large but non-shipping |
| `dist/` | Generated; do not edit by hand | committed artifact |

> [!IMPORTANT]
> The actual user-facing documentation prose lives in `src/html/components/docs/*.mdx` and `src/html/components/javascript/*.mdx`. These MDX files are the canonical, hand-written reference for install paths, the migration guide, RTL, deployment, and each plugin's API — treat them as authoritative source, not as demo filler.

---

## 4. The Build Pipeline

The build is orchestrated entirely through `package.json` scripts using `npm-run-all` for parallel/sequential composition. There is no Gulp/Grunt — v4 replaced AdminLTE 3's Gulp pipeline with direct CLI invocations.

### The full production build

`npm run production` runs: `clean → lint → compile → bundlewatch`.

```text
clean        rimraf dist
lint         npm-run-all --parallel js-lint css-lint docs-lint lockfile-lint
compile      css → js → assets → copy-assets → docs-compile → docs-format → flatten-build
bundlewatch  enforce per-asset size budgets (.bundlewatch.config.json)
```

### CSS pipeline (`npm run css`)

Four sequential stages: `css-compile → css-prefix → css-rtl → css-minify`.

| Stage | Tool | What it does |
|---|---|---|
| `css-compile` | `sass` | Compiles `src/scss/` → `dist/css/` (expanded style, source maps, `--load-path=node_modules`) |
| `css-prefix` | `postcss` + autoprefixer | Adds vendor prefixes in place (skips `*.rtl.*` and `*.min.*`) |
| `css-rtl` | `postcss` + rtlcss (`NODE_ENV=RTL`) | Generates `*.rtl.css` from the LTR output |
| `css-minify` | `clean-css-cli` | Produces `*.min.css` for both LTR and RTL, with source maps |

The RTL flip is driven by `src/config/postcss.config.mjs`, which conditionally enables `rtlcss` only when `process.env.NODE_ENV === 'RTL'`:

```js
plugins: {
  ...(process.env.NODE_ENV === 'RTL' ? { rtlcss: {} } : {}),
  autoprefixer: { cascade: false }
}
```

### JS pipeline (`npm run js`)

Two stages: `js-compile → js-minify`.

| Stage | Tool | What it does |
|---|---|---|
| `js-compile` | `rollup` (`src/config/rollup.config.js`) | Bundles `src/ts/adminlte.ts` → `dist/js/adminlte.js` (UMD, source map) |
| `js-minify` | `terser` | Produces `dist/js/adminlte.min.js` (2-pass compress, mangle, preserves `/^!/` banner, source map) |

The Rollup config emits a UMD bundle named `adminlte`, stamps a copyright banner from `package.json`, and runs source through `@rollup/plugin-typescript` against the root `tsconfig.json`:

```js
export default {
  input: 'src/ts/adminlte.ts',
  output: { file: 'dist/js/adminlte.js', format: 'umd', banner, name: 'adminlte' },
  plugins: [ typescript({ tsconfig: './tsconfig.json', sourceMap: true }) ]
}
```

### Assets and docs

| Script | Action |
|---|---|
| `assets` | `node src/config/assets.config.mjs` → `fs-extra` copies `src/assets` to `dist/assets` |
| `copy-assets` | Stages `dist/css`, `dist/js`, `dist/assets` into `src/html/public/` so Astro can serve them |
| `docs-compile` | `astro build` (config in `src/config/astro.config.mjs`) → `dist/html/` |
| `docs-format` | `prettier --write "dist/**/*.html"` |
| `flatten-build` | Moves `dist/html/*` up into `dist/`, removes the Astro intermediate, normalizes `docs.html` → `docs/index.html` |

### Watch mode (`npm run dev`)

`npm run dev` runs `watch` and `docs-serve` in parallel via `npm-run-all`. `watch` uses `concurrently` to run four nodemon watchers:

| Watcher | Triggers |
|---|---|
| `watch-css-main` | `src/scss/` `.scss` change → lint + compile + prefix + copy-assets |
| `watch-css-dist` | `dist/css/` `.css` change → regenerate RTL |
| `watch-js-main` | `src/ts/` `.ts` change → lint + compile + copy-assets |
| `watch-assets` | `src/assets/` change → re-run assets copy |

`docs-serve` runs `astro dev --open --port 3000`.

> [!NOTE]
> The dev server and docs share port 3000 (also configured in `astro.config.mjs` with `host: '0.0.0.0'`). The `.gitpod.yml` opens a preview on port 3000 with `npm install && npm run dev`.

---

## 5. JavaScript Plugin Internals

All plugins share a common shape: a constants block (DATA_KEY, EVENT_KEY, SELECTORs, CLASS_NAMEs), a class definition, and a `onDOMContentLoaded(() => …)` data-API block at the bottom that queries the DOM and wires listeners. They are imported and re-exported by `src/ts/adminlte.ts`:

```ts
import { onDOMContentLoaded } from './util/index.js'
import Layout from './layout.js'
import CardWidget from './card-widget.js'
import Treeview from './treeview.js'
import DirectChat from './direct-chat.js'
import FullScreen from './fullscreen.js'
import PushMenu from './push-menu.js'
import { initAccessibility } from './accessibility.js'

onDOMContentLoaded(() => {
  const accessibilityManager = initAccessibility({ /* all features true */ })
  accessibilityManager.addLandmarks()
})

export { Layout, CardWidget, Treeview, DirectChat, FullScreen, PushMenu, initAccessibility }
```

### 5.1 Shared utilities (`util/index.ts`)

The utility module is the foundation every plugin builds on:

| Export | Purpose |
|---|---|
| `onDOMContentLoaded(cb)` | Runs `cb` immediately if the DOM is ready, else queues it. Maintains a single shared callback array and registers one `DOMContentLoaded` listener — all plugins share the same listener. |
| `slideUp(target, duration=500)` | jQuery-free slide-up: animates height/margin/padding to 0 with inline transitions, then sets `display:none` and removes inline styles. |
| `slideDown(target, duration=500)` | Inverse — measures natural height, animates from 0. |
| `slideToggle(target, duration)` | Picks slideUp/slideDown by current `display`. |
| `hasDataAttribute(el, attr)` | `Object.hasOwn(el.dataset, attr)` (ES2022). |
| `getLastElement(elements)` | `Array.from(...).at(-1)` (ES2022). |
| `safePropertyAccess(obj, prop)` | Guarded property read; throws `Error` with `{ cause }` on failure. |

> [!NOTE]
> The single error string in the whole bundle comes from `safePropertyAccess`: `Failed to access property '<name>'` (`src/ts/util/index.ts`). This is the only thrown error in the runtime — the plugins otherwise fail silently/defensively (null-checks, `try/catch` around `localStorage`).

The `slideUp`/`slideDown` helpers are the animation engine for CardWidget (collapse/remove) and Treeview (submenu open/close).

### 5.2 PushMenu (`push-menu.ts`)

The most complex plugin. Manages sidebar state across desktop/mobile with optional persistence.

**Config** (type `Config`): `sidebarBreakpoint` (default `992`), `enablePersistence` (default `false`). Read from `data-sidebar-breakpoint` and `data-enable-persistence` on `.app-sidebar`.

**State model** — three body classes are the source of truth: `sidebar-collapse`, `sidebar-open`, `sidebar-mini`. Key methods:

- `expand()` — removes `sidebar-collapse`; on mobile adds `sidebar-open`; fires `open.lte.push-menu`.
- `collapse()` — removes `sidebar-open`, adds `sidebar-collapse`; fires `collapse.lte.push-menu`.
- `isMobileSize()` — `window.innerWidth <= sidebarBreakpoint`.
- `setupSidebarBreakPoint()` — reads the breakpoint from the `::before` pseudo-element `content` of the `[class*="sidebar-expand"]` element, so the JS breakpoint stays in sync with the CSS media queries.
- `updateStateByResponsiveLogic()` — on mobile, collapse unless explicitly open; on desktop, expand unless mini+collapsed.
- `loadSidebarState()` / `saveSidebarState()` / `clearSidebarState()` — localStorage under `lte.sidebar.state`, all wrapped in `try/catch` for private-browsing/quota safety and SSR (`globalThis.localStorage === undefined`) guards.

**Data-API block** creates a `.sidebar-overlay` element appended to `.app-wrapper`, wires touch (`touchstart`/`touchmove`/`touchend` with a moved-guard) and click handlers to collapse on outside-tap, wires all `[data-lte-toggle="sidebar"]` buttons, and re-runs the responsive logic on `window resize`.

### 5.3 Treeview (`treeview.ts`)

**Config:** `accordion` (default `true`), `animationSpeed` (default `300`). Read from `data-accordion` / `data-animation-speed` on the toggled element.

`open()` — if accordion, slides up sibling `.nav-item.menu-open` submenus first, then adds `menu-open` to this item and `slideDown`s its `.nav-treeview`; fires `expanded.lte.treeview`. `close()` removes `menu-open` and `slideUp`s. The data-API block uses **event delegation** from the document, guards against non-menu clicks (only acts when a `.nav-treeview` child exists), and dispatches `load.lte.treeview` for pre-opened (`menu-open`) submenus on load.

### 5.4 CardWidget (`card-widget.ts`)

**Config:** `animationSpeed` (500), and three trigger selectors. The class resolves `_parent` as the closest `.card` (or the element itself if it *is* a card). Methods: `collapse()`/`expand()` (uses `:scope > .card-body, :scope > .card-footer` so nested cards are unaffected), `remove()`, `maximize()`/`minimize()` (sets fixed sizing, toggles `maximized-card` on both `.card` and `<html>`, tracks `was-collapsed`), and the `toggle*` variants. Each fires its corresponding `*.lte.card-widget` event.

### 5.5 DirectChat (`direct-chat.ts`)

The simplest plugin. `toggle()` flips `direct-chat-contacts-open` on the `.direct-chat` container and fires `expanded`/`collapsed.lte.direct-chat`. Constructor takes only the element — no config.

### 5.6 FullScreen (`fullscreen.ts`)

Wraps the browser Fullscreen API. `inFullScreen()` calls `document.documentElement.requestFullscreen()` and toggles `.d-none` on `[data-lte-icon="maximize"]`/`[data-lte-icon="minimize"]`; `outFullscreen()` calls `document.exitFullscreen()`. `toggleFullScreen()` checks `document.fullscreenEnabled` and `document.fullscreenElement` first.

> [!TIP]
> The `.d-none` toggle (rather than inline `display`) is deliberate — it fixed issue #6021 where the maximize icon shifted position. It keeps the icon library's natural `display` value (some icon fonts use `inline-block`), so it works with FontAwesome, Lucide, Tabler Icons, etc.

### 5.7 Layout (`layout.ts`)

`holdTransition(time=100)` adds `hold-transition` to `<body>` and removes it after `time` ms (clearing any prior timer). The data-API block instantiates `Layout(document.body)`, holds transitions on `window resize` (200 ms), and adds `app-loaded` to `<body>` 400 ms after load.

### 5.8 AccessibilityManager (`accessibility.ts`)

The largest module. Constructed with a `Partial<AccessibilityConfig>` (all five flags default `true`) and runs an `init()` that conditionally installs each feature:

| Method | WCAG | Mechanism |
|---|---|---|
| `createLiveRegion()` | 4.1.3 | Appends an `aria-live="polite"`, `role="status"` div (`#live-region`) |
| `addSkipLinks()` / `ensureSkipTargets()` | 2.4.1 | Inserts skip links; assigns `id`/`tabindex` to main/nav targets |
| `initFocusManagement()` | 2.4.3/2.4.7 | Tab/Escape handlers; modal + dropdown focus on Bootstrap events |
| `initKeyboardNavigation()` / `handleMenuNavigation()` | 2.1.1 | Arrow/Home/End nav inside `.nav`, `.navbar-nav`, `.dropdown-menu` |
| `respectReducedMotion()` | 2.3.3 | On `prefers-reduced-motion`, adds `reduce-motion` + injects a duration-killing `<style>` |
| `initErrorAnnouncements()` | 3.3.1 | A `MutationObserver` on `document.body` announces `.alert-danger`/`.invalid-feedback` (assertive) and `.alert-success` (polite) |
| `initTableAccessibility()` | 1.3.1 | Adds `role`, `scope` on `th`, and a `caption` from `title` |
| `initFormAccessibility()` / `handleFormError()` | 3.3.1/3.3.2 | Auto-`aria-label`s from placeholder, marks required, appends `.invalid-feedback` on `invalid` events |

Public API: `announce(message, priority)`, `focusElement(selector)`, `trapFocus(container)`, `addLandmarks()`. Plus a standalone `accessibilityUtils` export with `checkColorContrast`, `generateId`, and `isFocusable`.

> [!IMPORTANT]
> `addLandmarks()` deliberately **skips `<ul>`/`<ol>` elements** when assigning `role="navigation"`. Assigning a navigation role to a list strips its implicit list semantics and orphans its `<li>` children for screen readers — this fixed issue #6038 (a Lighthouse "list items not contained within a list parent" failure). When auditing or extending this module, preserve that guard.

---

## 6. The SCSS Architecture

The stylesheet is a modular SCSS system imported through one master file, `src/scss/adminlte.scss`. It layers on top of Bootstrap rather than replacing it.

### Layering model

```text
_bootstrap-variables.scss   ← Bootstrap variable overrides ($primary, breakpoints, spacing)
_variables.scss             ← AdminLTE variables ($lte-sidebar-width, $lte-app-header-height, …)
_variables-dark.scss        ← dark-mode variable overrides
   ↓ (all use !default — consumer overrides win if declared first)
Bootstrap import
   ↓
_app-wrapper / _app-header / _app-sidebar / _app-main / _app-content / _app-footer  (layout grid)
   ↓
component partials (_cards, _small-box, _info-box, _timeline, _direct-chat, _callouts, _toasts,
                    _progress-bars, _dropdown, _table, _compact-mode, _accessibility, …)
   ↓
mixins/ (_animations, _scrollbar) and parts/ (_core, _components, _extra-components, _pages, _miscellaneous)
```

### Key variables (defaults)

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

// Sidebar colours (light)
$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;
```

Because all variables carry `!default`, downstream projects override them by declaring values *before* `@import "node_modules/admin-lte/src/scss/adminlte"`.

### The wrapper grid

`.app-wrapper` is a CSS-grid container; `.app-header`, `.app-sidebar`, `.app-main`, `.app-footer` are grid areas positioned by class regardless of source order. The responsive sidebar breakpoint is encoded in CSS via media queries on the `[class*="sidebar-expand"]` element, with the numeric value exposed through a `::before` `content` value that `PushMenu.setupSidebarBreakPoint()` reads back — this is how the SCSS breakpoint and the JS breakpoint stay synchronized without duplication.

### Stylelint constraints

`.stylelintrc.json` extends `stylelint-config-twbs-bootstrap` and adds notable disallow lists for `.scss` files:

- `function-disallowed-list`: `calc`, `lighten`, `darken` (use CSS `calc()` sparingly / prefer relative color syntax)
- `property-disallowed-list`: `border-radius` (+ corners) and `transition` (use the Bootstrap/AdminLTE variables and mixins instead of hardcoding)
- `declaration-property-value-disallowed-list`: `border: none`, `outline: none` (use `0`)

> [!NOTE]
> These lint rules are why the codebase avoids raw `transition`/`border-radius` declarations — they enforce going through variables so theming stays consistent and RTL/dark mode keep working.

---

## 7. The Documentation Site (Astro)

`src/html/` is a self-contained Astro project that builds the demo dashboard and the documentation into static HTML. It is **not** part of the distributable template — it's the project's own marketing/docs site, deployed separately (Netlify).

### Configuration (`src/config/astro.config.mjs`)

```js
export default defineConfig({
  output: 'static',
  build: { format: 'file' },        // page.html, not page/index.html
  compressHTML: false,
  markdown: { shikiConfig: { theme: 'dark-plus' } },
  integrations: [mdx()],
  srcDir: './src/html',
  publicDir: './src/html/public',   // staged dist/ assets live here
  cacheDir: './dist/.astro',
  outDir: './dist/html',
  server: { host: '0.0.0.0', port: 3000 }
})
```

### Structure

- `src/html/pages/*.astro` — the routed pages: the three dashboards (`index`, `index2`, `index3`), `docs/*` (one page per doc topic), `layout/*` (the 11 layout demos), `pages/*` (apps: calendar, kanban, chat, file-manager, invoice, profile, settings, pricing, faq, 404, 500, maintenance), `forms/*`, `tables/*`, `UI/*`, `widgets/*`, `examples/*` (login/register/lockscreen).
- `src/html/components/` — `.astro` layout partials (`_head`, `_scripts`, dashboard `_topbar`/`_sidenav`/`_footer`) and the `.mdx` documentation content (`docs/*.mdx`, `javascript/*.mdx`).
- `@components/*` is path-aliased to `src/html/components/*` in `tsconfig.json`.

The build copies `dist/css`, `dist/js`, `dist/assets` into `src/html/public/` (via `copy-assets`) so the demo pages reference the just-built template, then `flatten-build` lifts `dist/html/*` to the `dist/` root for publishing.

> [!NOTE]
> Because the docs site reads from `dist/`, you must build the template (`npm run css js assets copy-assets`) before the docs site will render the current styles. `npm run dev` wires this automatically via `copy-assets` in the watchers.

---

## 8. Configuration Reference

AdminLTE has **no runtime configuration files** — there is no app config, no environment variables consumed at runtime, no secrets. The repo's "configuration" is entirely build-time and tooling. The briefing's ground-truth scan correctly returned *no application config files*: a template has none.

### Build & tooling config files

| File | Tool | Purpose |
|---|---|---|
| `tsconfig.json` | TypeScript | `strict` everything, `target: es2022`, `lib: [es2022, DOM]`, `module: esnext`, `moduleResolution: bundler`, includes `src/ts/**/*`, `@components/*` path alias |
| `eslint.config.js` | ESLint (flat) | `@eslint/js` recommended + `typescript-eslint` + `eslint-plugin-unicorn` (flat/recommended) + `eslint-plugin-astro`; custom rule tweaks (`comma-dangle: never`, 2-space indent, `semi: off`) |
| `.stylelintrc.json` | Stylelint | twbs-bootstrap config + SCSS disallow lists (see §6) |
| `src/config/rollup.config.js` | Rollup | UMD bundle, banner, TS plugin |
| `src/config/astro.config.mjs` | Astro | Static docs build |
| `src/config/postcss.config.mjs` | PostCSS | autoprefixer + conditional rtlcss |
| `src/config/assets.config.mjs` | fs-extra | Copy assets |
| `.browserslistrc` | Browserslist | Target browsers (consumed by autoprefixer) |
| `.bundlewatch.config.json` | bundlewatch | Per-asset size budgets |
| `.prettierrc`, `.editorconfig`, `.gitattributes` | formatting | Code style |
| `netlify.toml` | Netlify | Docs-site deploy (static publish of `dist/`) |

### TypeScript compiler options (notable)

```json
{
  "strict": true, "strictNullChecks": true, "strictPropertyInitialization": true,
  "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true,
  "noFallthroughCasesInSwitch": true, "noImplicitAny": true,
  "target": "es2022", "lib": ["es2022", "DOM"],
  "module": "esnext", "moduleResolution": "bundler", "removeComments": true
}
```

The strictness here is meaningful: every plugin is written to satisfy `strictNullChecks` and `strictPropertyInitialization`, which is why you see pervasive optional chaining and `as HTMLElement | undefined` casts in the source.

### Bundle size budgets (`.bundlewatch.config.json`)

| Asset | Max size |
|---|---|
| `dist/css/adminlte.css` | 50 kB |
| `dist/css/adminlte.min.css` | 46 kB |
| `dist/css/adminlte.rtl.css` | 50 kB |
| `dist/css/adminlte.rtl.min.css` | 46 kB |
| `dist/js/adminlte.js` | 9 kB |
| `dist/js/adminlte.min.js` | 5.5 kB |

`npm run bundlewatch` fails the build if any artifact crosses its budget — a hard guard against accidental bloat.

---

## 9. Build & Release Operations

### Building locally

```bash
npm install            # respects the package.json overrides block (npm 8.3+)
npm run production     # clean + lint + compile + bundlewatch → dist/
```

> [!TIP]
> `npm run build` (just `clean + compile`) is the faster path for development/testing — it skips linting and the bundlewatch gate.

### The committed `dist/`

Unusually, **`dist/` is committed to the repository**. The `netlify.toml` comment explains why:

```toml
[build]
  publish = "dist"
  command = "echo 'Static deploy of committed dist/'"
```

> [!WARNING]
> Because `dist/` is committed and the Netlify production deploy simply *publishes the committed files* (it runs no build), the `dist/` artifacts can drift from `src/` if a contributor edits source without rebuilding. Always run `npm run production` and commit the regenerated `dist/` as part of any change that touches `src/ts/` or `src/scss/`. The repo history shows explicit "rebuild dist" commits for this reason. The Netlify static-publish approach was chosen to avoid `npm install` failing on Netlify due to Node-version drift (the build pins `NODE_VERSION = "22"` to match CI).

### Linting before release

```bash
npm run lint   # parallel: js-lint, css-lint, docs-lint, lockfile-lint
```

`lockfile-lint` enforces that every dependency in `package-lock.json` resolves over `https:` from the npm registry — a supply-chain guard against lockfile tampering.

### Versioning & distribution channels

| Channel | Package | Notes |
|---|---|---|
| npm | `admin-lte` | `main: dist/js/adminlte.min.js`, `style`/`sass` fields point at dist/src |
| Packagist (Composer) | `almasaeed2010/adminlte` | Same source tree |
| jsDelivr CDN | mirrors npm | Versioned URLs (`@4.0.0`) |

Releases are cut from the `master` branch. The changelog lives on the [GitHub releases page](https://github.com/ColorlibHQ/AdminLTE/releases) and `CHANGELOG.md`.

---

## 10. Deployment & Performance

AdminLTE deploys as static files. The choices below materially affect page weight and load time.

### What to deploy

Ship `dist/` plus your own HTML/server-rendered templates:

```text
dist/
├── css/   adminlte.css, adminlte.min.css, adminlte.rtl.css, adminlte.rtl.min.css
├── js/    adminlte.js, adminlte.min.js
└── assets/  (demo images — replace with your own)
```

Do **not** ship `src/`, `node_modules/`, or the demo HTML when embedding AdminLTE into your own app.

### Production checklist

- [ ] Serve the **`.min`** variants (`adminlte.min.css`, `adminlte.min.js`) — never the unminified dev builds.
- [ ] Enable gzip/brotli for `text/css`, `application/javascript`, `text/html`, `image/svg+xml` at the server/CDN.
- [ ] Set cache headers — short `max-age` + `?v=4.0.0` query string, or fingerprinted filenames with `max-age=31536000, immutable`.
- [ ] Add Subresource Integrity (`integrity` + `crossorigin`) hashes if loading from a public CDN.
- [ ] Use `defer` (not `async`) on the AdminLTE script so it runs after Bootstrap/Popper are parsed.
- [ ] Strip `*.map`, `**/html/`, `**/.astro/` before uploading a demo `dist/` verbatim.

> [!CAUTION]
> Do not use `async` on `adminlte.min.js`. The bundle waits for `DOMContentLoaded`, but `async` could let it execute before Bootstrap and Popper finish parsing — listeners would attach to elements/APIs that don't exist yet. `defer` is the supported and recommended attribute.

### Bundle weights (gzipped)

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

A full first-page payload (HTML + Bootstrap + AdminLTE + Bootstrap Icons + Popper + OverlayScrollbars) is around **180 KB gzipped** from CDN.

### Loading order (authoritative)

```html
<head>
  <!-- 1 -->  Bootstrap Icons font CSS
  <!-- 2 -->  OverlayScrollbars CSS
  <!-- 3 -->  AdminLTE CSS (imports Bootstrap CSS)
</head>
<body>
  …
  <!-- 4 -->  Popper
  <!-- 5 -->  Bootstrap JS
  <!-- 6 -->  OverlayScrollbars JS
  <!-- 7 -->  AdminLTE JS (must load AFTER Bootstrap)
</body>
```

### Advanced

- **Critical CSS** — inline above-the-fold styles and `preload` the rest of `adminlte.min.css` (Critters/Penthouse). Usually overkill for behind-login dashboards.
- **Per-route CSS** — core CSS is small, but page-specific integrations (charts, calendars, tables) add up; load them only where used. Code-split heavy integrations in SPAs.
- **Service worker** — AdminLTE is static files; precache them with Workbox by URL + revision.
- **Cloudflare** — default Browser Cache TTL is 4 hours; purge affected paths after a release or users see stale CSS/JS.

---

## 11. Security Analysis

AdminLTE is a **UI template** with no server, no auth, no data handling, and no secrets. Its security surface is therefore narrow — but there are real considerations.

### The project's own security posture

`SECURITY.md` and the README codify a clear development/production separation:

**Deploy** only compiled production assets (`dist/js/adminlte.min.js`, `dist/css/adminlte.min.css`) plus your own files. **Do not deploy** `node_modules/`, the demo/example HTML (`index.html`, `index2.html`, `index3.html`, etc.), `src/`, or dev config.

> [!IMPORTANT]
> **CVE-2021-36471 is disputed.** It refers to demo pages being reachable when developers incorrectly deploy the *example* HTML files to production — not to a vulnerability in AdminLTE's code. v4 maintains a clear separation between demo and production assets. The mitigation is operational: never publish the demo HTML to a production web root.

### Supply-chain controls

| Control | Mechanism |
|---|---|
| Lockfile integrity | `lockfile-lint` enforces `https:` npm-registry resolution for every dependency |
| Dependency overrides | `package.json` `overrides` pins `yaml` and the stylelint config peer so `npm install` runs clean with **0 reported vulnerabilities** (no `--legacy-peer-deps`) |
| CDN integrity | Docs recommend SRI hashes for all CDN-loaded assets (AdminLTE, Bootstrap, Popper, OverlayScrollbars, Bootstrap Icons) |
| Source maps | Documented as a leak vector — strip `*.map` from production uploads |

### XSS / client-side considerations for integrators

Because AdminLTE is markup you fill with your own data, **the XSS surface is yours, not AdminLTE's**. Notable points:

- AdminLTE's plugins set DOM attributes and classes; they do not inject user-controlled HTML. The one DOM-string injection in the accessibility module (`announce`) writes to `textContent`, not `innerHTML`, so it is XSS-safe.
- ESLint's `astro/no-set-html-directive` is turned **off** in this repo (the docs site uses `set:html` for trusted MDX). When you adopt patterns from the demo, do **not** carry untrusted data into `innerHTML`/`set:html`.
- The recipe patterns that `fetch()` HTML into a card (`body.innerHTML = html`) assume *trusted* server responses — sanitize or switch to `textContent` if the source isn't yours.

### Accessibility as a quality/compliance control

The bundled `AccessibilityManager` provides WCAG 2.1 AA features automatically (see §5.8 and `ACCESSIBILITY-COMPLIANCE.md`). For regulated deployments (Section 508, EN 301 549), this is a meaningful baseline — but verify your *own* content (color contrast of custom themes, alt text, form labels) since AdminLTE can only harden the shell.

---

## 12. Code Quality & Conventions

### Language & style

- **TypeScript, strict mode, ES2022 target.** Pervasive null-safety (`?.`, `as T | undefined`), `Object.hasOwn`, `Array.at(-1)`, `globalThis.*`.
- **Plugin idiom is uniform:** constants block (DATA_KEY / EVENT_KEY / SELECTOR_* / CLASS_NAME_*) → class → `onDOMContentLoaded` data-API block → `export default`. Learning one plugin means you can read all seven.
- **Event naming convention:** `<verb>.lte.<plugin-key>` (e.g. `collapsed.lte.card-widget`). DATA_KEY is `lte.<plugin>`.
- **No jQuery, no framework.** All DOM work is native (`querySelector`, `addEventListener`, `classList`, `dataset`).
- **Defensive by default:** localStorage access, fullscreen calls, and DOM lookups are all guarded; the bundle degrades rather than throws.

### Lint enforcement

- **ESLint** (flat config): `typescript-eslint` recommended + `unicorn` flat/recommended + `astro`. Custom: `comma-dangle: never`, 2-space indent, `multiline-ternary: always-multiline`, `operator-linebreak: after`, many `unicorn/*` rules selectively disabled (e.g. `prefer-query-selector` off, `prevent-abbreviations` off).
- **Stylelint** (twbs-bootstrap) with the disallow lists in §6.
- Linters run in parallel in `npm run lint`, and as the `lint` stage of `npm run production`.

### Conventions worth preserving when contributing

| Convention | Why |
|---|---|
| Read config from `data-*`, fall back to a `Default`/`Defaults` object | Keeps the declarative data API working without JS |
| Animate via `slideUp`/`slideDown` from `util/` | One animation engine; consistent timing |
| Guard `localStorage` and browser APIs | SSR + private-browsing + quota safety |
| Use `.d-none` / class toggles, not inline `display`/`style` | Survives icon-library and theme differences (see #6021) |
| Don't put `role="navigation"` on lists | Preserves list semantics (see #6038) |

---

## 13. Testing & CI

AdminLTE 4 ships **no unit-test suite** — there is no `tests/` directory, no Jest/Vitest config, and no test runner in `package.json`. Quality is enforced through a combination of static analysis and size gates rather than behavioral tests:

| Gate | Tool | Catches |
|---|---|---|
| Type checking | `tsc` (via Rollup's TS plugin, strict) | Type errors, null-safety violations |
| JS lint | ESLint | Style + a class of bugs (unused vars, unsafe patterns) |
| CSS lint | Stylelint | Disallowed properties/functions, inconsistencies |
| Docs lint | `astro check` (`docs-lint`) | Astro/TS errors in the docs site |
| Lockfile lint | `lockfile-lint` | Supply-chain tampering |
| Size budget | `bundlewatch` | Bundle bloat |

CI runs on Node 22 (per `netlify.toml` and the README's "Node 22 LTS in CI" note). The GitHub Actions workflows live under `.github/workflows/` (referenced by `netlify.toml`, not included in the analyzed source set).

> [!WARNING]
> The absence of behavioral tests means regressions in plugin logic (e.g. the sidebar state machine, card maximize/restore, treeview accordion) can only be caught by manual demo-page testing. This is the single largest quality gap — see [Improvements](#14-potential-improvements).

### Manual verification surface

Because the demo site exercises every plugin and layout, the practical "test suite" is the demo pages: `layout/*` for the 11 layout modes, `pages/*` and `widgets/*` for components, and the dashboards (`index`, `index2`, `index3`) for the integrated experience. After any `src/ts/` or `src/scss/` change, run `npm run dev` and click through the affected demos.

---

## 14. Potential Improvements

Observations drawn from the source. None are blockers; they're the natural next steps for a maturing v4.

1. **Add a behavioral test suite.** The plugins are small, pure-ish DOM classes — ideal for Vitest + jsdom or Playwright component tests. Priority targets: `PushMenu` state machine (mobile/desktop/persistence transitions), `CardWidget` maximize↔minimize with `was-collapsed` tracking, `Treeview` accordion sibling-closing, and the `AccessibilityManager` MutationObserver. This is the highest-leverage improvement given the committed-`dist/` model where a logic regression ships silently.

2. **Automate `dist/` regeneration in CI.** Since `dist/` is committed and Netlify publishes it verbatim, a CI check that rebuilds and diffs `dist/` against the committed files would catch source/dist drift before merge.

3. **Per-plugin entry points for tree-shaking.** The bundle is a single UMD file exporting all seven plugins. Consumers who need only `PushMenu` still ship the whole bundle unless they import from `src/ts/` (which requires TypeScript). Publishing per-plugin ESM entry points would let bundlers drop unused plugins.

4. **Document the SCSS variable surface exhaustively.** The customization docs list the most-overridden variables, but `_variables.scss` has many more. A generated variable reference (name, default, what it controls) would reduce guesswork for theme authors.

5. **Consolidate the dark-mode storage key story.** The FAQ references an `lte-theme` localStorage key while the color-mode toggle snippet uses `theme`. Aligning the docs (and ideally shipping the toggle as a tiny optional module with a documented key) would remove a real source of confusion.

6. **Refactor `convertPathToHtml` (`src/utils/index.js`).** It's a hardcoded `count === 2..5` ladder for relative-path depth used by the docs build. A small loop (`'../'.repeat(count - 2)` style) would handle arbitrary depth and remove the magic numbers.

7. **Tighten the `safePropertyAccess` contract.** It's the only thrower in the runtime and currently appears unused by the plugins. Either wire it where dynamic property access happens or drop it to keep the util surface minimal.

8. **Surface plugin config more uniformly.** `CardWidget` reads *no* `data-*` config (only programmatic), while `PushMenu`/`Treeview` read several. Giving `CardWidget` a `data-animation-speed` attribute would make the declarative API consistent across plugins.

---

## 15. Contributing

The contribution flow (from the README):

1. Install Node.js LTS (18+, 22 recommended).
2. Clone the repo and switch to the `master` branch.
3. `npm install` (installs from `package.json`; the `overrides` block keeps peers clean).
4. `npm start` (alias for `npm run dev`) to launch the dev server at `http://localhost:3000`.
5. Make changes in `src/` — `src/ts/` for behavior, `src/scss/` for styles, `src/html/` for docs/demos.
6. **Rebuild and commit `dist/`** if you changed `src/ts/` or `src/scss/` (`npm run production`).
7. Run `npm run lint` and fix any failures.
8. Send a PR against `master`.

> [!TIP]
> Pre-opened submenus (`menu-open`), the demo dashboards, and `layout/*` pages are your fastest manual regression check. The CI gates (lint, type-check, bundlewatch) will catch the rest, but they won't catch behavioral regressions — verify interactive changes by hand.

Discussion happens in [GitHub Discussions](https://github.com/ColorlibHQ/AdminLTE/discussions); bugs and feature requests go to [Issues](https://github.com/ColorlibHQ/AdminLTE/issues). There's also a [Discord](https://discord.gg/jfdvjwFqfz).

---

## 16. Quick Reference

### Plugin class summary

| Class | File | Constructor | Key config defaults |
|---|---|---|---|
| `Layout` | `layout.ts` | `(element)` | — (auto on `<body>`) |
| `PushMenu` | `push-menu.ts` | `(element, config)` | `sidebarBreakpoint: 992`, `enablePersistence: false` |
| `Treeview` | `treeview.ts` | `(element, config)` | `accordion: true`, `animationSpeed: 300` |
| `CardWidget` | `card-widget.ts` | `(element, config)` | `animationSpeed: 500` |
| `DirectChat` | `direct-chat.ts` | `(element)` | — |
| `FullScreen` | `fullscreen.ts` | `(element)` | — |
| `AccessibilityManager` | `accessibility.ts` | `(config)` via `initAccessibility()` | all five features `true` |

### localStorage keys

| Key | Owner | Values |
|---|---|---|
| `lte.sidebar.state` | PushMenu (when `enablePersistence`) | `sidebar-open` / `sidebar-collapse` |
| `theme` | Docs color-mode snippet (not in bundle) | `light` / `dark` / `auto` |

### Build commands

| Command | Purpose |
|---|---|
| `npm install` | Install deps (npm 8.3+ for `overrides`) |
| `npm run dev` / `npm start` | Dev server + docs at :3000 |
| `npm run build` | Fast build (clean + compile) |
| `npm run production` | Full build (clean + lint + compile + bundlewatch) |
| `npm run css` / `npm run js` | Build one half |
| `npm run lint` | All linters |
| `npm run bundlewatch` | Size-budget check |

### Distributable artifacts

| File | Use |
|---|---|
| `dist/css/adminlte.min.css` | Production stylesheet (LTR) |
| `dist/css/adminlte.rtl.min.css` | Production stylesheet (RTL) |
| `dist/js/adminlte.min.js` | Production script (UMD, exports `adminlte`) |
| `dist/assets/` | Demo images (replace with your own) |

### Runtime peer dependencies

| Dependency | Pinned | Role |
|---|---|---|
| Bootstrap | 5.3.8 | Component framework + CSS-variable theming |
| Popper | 2.11.8 | Bootstrap overlays |
| Bootstrap Icons | 1.13.1 | Default icon font |
| OverlayScrollbars | 2.11.0 | Sidebar scrollbar |

### Key facts (verified)

- Version **4.0.0**, MIT, no jQuery, vanilla TypeScript.
- **7 plugins**, single UMD bundle (`adminlte`), ~5 KB gzipped JS / ~40 KB gzipped CSS.
- **No runtime config, no server, no tests directory.** Quality via lint + type-check + bundlewatch.
- **`dist/` is committed**; Netlify publishes it verbatim. Rebuild on every `src/` change.
- TypeScript target **ES2022**, strict; Node **18+** (22 in CI); **no IE 11**.

---

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