Jesse A. Green / notebook

AdminLTE 4 Admin & Architecture Guide

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.

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
  2. System Architecture
  3. Repository Structure
  4. The Build Pipeline
  5. JavaScript Plugin Internals
  6. The SCSS Architecture
  7. The Documentation Site (Astro)
  8. Configuration Reference
  9. Build & Release Operations
  10. Deployment & Performance
  11. Security Analysis
  12. Code Quality & Conventions
  13. Testing & CI
  14. Potential Improvements
  15. Contributing
  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).

                          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:

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

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.

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':

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:

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:

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 slideDowns its .nav-treeview; fires expanded.lte.treeview. close() removes menu-open and slideUps. 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-labels 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

_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)

// 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)

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)

{
  "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

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:

[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

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

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)

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

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; bugs and feature requests go to Issues. There's also a Discord.


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.