AdminLTE Documentation

AdminLTE - Free admin dashboard template based on Bootstrap 5

RAW Doc

File: README.md

[AdminLTE β€” Bootstrap 5 Admin Dashboard](https://adminlte.io)






AdminLTE is the most popular open-source admin dashboard template β€” fully responsive,
built on Bootstrap 5.3 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 Β·
Documentation Β·
Framework Editions Β·
Premium Templates

Framework editions

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

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

All npm scripts

  • 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

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

Highlights

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

Breaking changes from v3

  • 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: 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, live demo). Nothing is added to adminlte.css.

See the dedicated Migration from v3 guide.

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,
including editions built for the same stacks AdminLTE integrates with:

| Admindek

text
The natural next step from AdminLTE: Bootstrap 5 + vanilla JS, 100+ components, dark/light modes, RTL, 10 color presets.
  Also for [Laravel](https://dashboardpack.com/theme-details/admindek-dashboard-laravel/?utm_source=github&utm_medium=readme&utm_campaign=adminlte) Β·
  [Next.js](https://dashboardpack.com/theme-details/admindek-nextjs/?utm_source=github&utm_medium=readme&utm_campaign=adminlte) Β·
  [Angular](https://dashboardpack.com/theme-details/admindek-dashboard-angular/?utm_source=github&utm_medium=readme&utm_campaign=adminlte) | [**Apex Dashboard**](https://dashboardpack.com/theme-details/apex-dashboard-nextjs/?utm_source=github&utm_medium=readme&utm_campaign=adminlte)
  
  5 dashboard variants, 20+ app pages, 125+ routes, full CRUD β€” in your backend's native stack.
  For [Next.js](https://dashboardpack.com/theme-details/apex-dashboard-nextjs/?utm_source=github&utm_medium=readme&utm_campaign=adminlte) Β·
  [Laravel](https://dashboardpack.com/theme-details/apex-dashboard-laravel/?utm_source=github&utm_medium=readme&utm_campaign=adminlte) Β·
  [Django](https://dashboardpack.com/theme-details/apex-dashboard-django/?utm_source=github&utm_medium=readme&utm_campaign=adminlte) Β·
  [Angular](https://dashboardpack.com/theme-details/apex-dashboard-angular/?utm_source=github&utm_medium=readme&utm_campaign=adminlte) |

| --- | --- |
| Zenith Dashboard β€” Django

text
Achromatic, ultra-minimal design as a ready-to-run Django project: 50+ pages, 6 dashboards, live theme customizer. | [**Haze β€” Nuxt**](https://dashboardpack.com/theme-details/haze-dashboard-nuxt/?utm_source=github&utm_medium=readme&utm_campaign=adminlte)
  
  Nuxt 4 + Nuxt UI v4 + Tailwind CSS v4. 92+ pages, 7 layouts, 5 dashboards, RTL, i18n, mock API layer. |

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.

Sponsorship

Support AdminLTE development by becoming a sponsor or donor.

Our sponsors

Contributing

Contributions are highly welcome:

  1. Install Node.js (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 licensed under
MIT. AdminLTE.io reserves the right to change
the license of future releases.

The charts on the demo pages are drawn with Chart.js (MIT),
loaded from a CDN on the pages that use it β€” it is not a dependency of the admin-lte package.

Image credits

Pixeden,
Graphicsfuel,
Pickaface,
Unsplash,
Uifaces,
Unavatar


File: ACCESSIBILITY-COMPLIANCE.md

AdminLTE Accessibility Statement

Overview

AdminLTE 4 is built with accessibility in mind and aims for WCAG 2.1 AA conformance. This document describes honestly what is implemented today, what is partially implemented, and what is still on the roadmap β€” so you know exactly what you get and what your application still needs to provide.

Important: a template can only ever be a starting point. Accessible markup in the demo pages does not make your application compliant β€” you remain responsible for testing the pages you build.

βœ… Implemented

Markup (demo pages)

  • Semantic landmark structure on the app shell (<nav>, <main>, <aside>, <footer>) β€” non-list .nav containers receive role="navigation" automatically (#6038)
  • Exactly one <h1> per page (the page title in the content header)
  • Breadcrumbs wrapped in <nav aria-label="breadcrumb"> with aria-current="page"
  • aria-label on all icon-only controls (card tools, topbar toggles, sidebar toggle)
  • Form inputs associated with <label> elements (visually hidden where the design is placeholder-driven, floating labels on the v2 auth pages)
  • Table headers with scope attributes
  • lang="en" declared, descriptive page titles, meta name="color-scheme"

Behavior (`accessibility.ts` and components)

  • Live region announcements (WCAG 4.1.3) β€” a single polite #live-region, with a public announce() API; alerts inserted into the DOM are announced automatically
  • Skip links to main content and navigation, injected once and reused across Turbo navigations (WCAG 2.4.1)
  • Treeview state exposure β€” sidebar submenu toggles carry aria-expanded, kept in sync by the Treeview component
  • Modal focus restoration β€” the triggering element is captured on show.bs.modal and restored on close (skipped if it has left the document)
  • Escape closes open dropdowns; Bootstrap's own modal keyboard handling is left untouched
  • Arrow-key navigation in menus and dropdowns β€” only when focus is on a menu item; never intercepts keys inside inputs, textareas, selects, or contenteditable elements
  • Reduced motion β€” prefers-reduced-motion disables smooth scrolling and shortens animations; prefers-contrast: more styles in the CSS
  • Theme-flash prevention and dark mode via Bootstrap color modes, respecting prefers-color-scheme (#6043)
  • Form error identification (WCAG 3.3.1/3.3.2) β€” validation errors get an invalid-feedback node wired via aria-describedby (appended to existing descriptions, not replacing them) and are announced assertively

Explicit non-goals

  • No global focus wrapping. Focus is deliberately not trapped at the page edges β€” wrapping Tab at the document boundary would itself violate WCAG 2.1.2 (No Keyboard Trap). Focus trapping is applied only inside modal dialogs.

⚠️ Partial / known gaps

These are known, tracked limitations β€” pull requests welcome:

  • Treeview and push-menu have no dedicated keyboard interaction pattern beyond normal Tab/Enter link behavior (no roving tabindex, no Home/End within the tree). aria-expanded is stamped by JS; static markup without JS does not carry it.
  • Drag-and-drop demos (kanban, sortable dashboard cards) have no keyboard alternative. SortableJS does not provide one; treat these demos as visual examples only (WCAG 2.5.7 gap).
  • Touch target sizes are not enforced globally. Card tool buttons are smaller than 44Γ—44 px; an opt-in .touch-target utility class exists in _accessibility.scss.
  • Color contrast is not guaranteed for every Bootstrap color utility combination you may compose. The accessibilityUtils.checkColorContrast() helper (supports rgb()/hex) can verify your combinations.
  • No automated accessibility tests run in CI yet (axe/pa11y integration is on the roadmap). Claims in this document are verified manually and are point-in-time.

πŸ”§ JavaScript API

typescript
import { initAccessibility, accessibilityUtils } from 'admin-lte'

const accessibility = initAccessibility({
  announcements: true,      // live region + automatic alert announcements
  skipLinks: true,          // inject skip links
  focusManagement: true,    // modal focus restore, Escape handling
  keyboardNavigation: true, // arrow keys in menus
  reducedMotion: true       // respect prefers-reduced-motion
})

accessibility.announce('Data saved successfully', 'polite')
accessibility.focusElement('#error-summary')
accessibility.trapFocus(customDialogElement) // for non-Bootstrap dialogs

// Contrast checking (rgb() and hex supported)
accessibilityUtils.checkColorContrast('#000000', '#ffffff') // { ratio: 21, passes: true }

The module is initialized automatically by adminlte.js. All document-level listeners are registered against the Turbo lifecycle signal, so Hotwired Turbo navigations neither leak listeners nor duplicate injected nodes.

πŸ§ͺ How to test your pages

  • Automated: axe-core, WAVE, Lighthouse accessibility audit
  • Keyboard: navigate your entire flow with Tab/Shift+Tab/Enter/Escape only; confirm focus is always visible and never trapped
  • Screen readers: NVDA (Windows, free), VoiceOver (macOS/iOS), JAWS
  • Zoom: verify layouts at 200% zoom and 320px viewport width
  • Motion: enable "reduce motion" in your OS and confirm animations calm down

πŸ—ΊοΈ Roadmap

  • axe/pa11y checks in CI against the built demo pages
  • Keyboard interaction pattern (roving tabindex) for the sidebar treeview
  • aria-expanded/aria-controls in the static demo markup (not only JS-stamped)
  • Audit and document contrast for all shipped color variants
  • RTL accessibility review

πŸ“š Resources


File: AGENTS.md

Repository Guidelines

Project Structure & Module Organization

AdminLTE 4 is a Bootstrap 5 dashboard template with vanilla TypeScript components.

  • src/ts/: component behavior; adminlte.ts is the library entry point, and util/ holds shared helpers.
  • src/scss/: Sass styles, component partials, mixins, and optional color themes.
  • src/html/: Astro demo/documentation pages and reusable Astro/MDX components.
  • src/assets/: source images and other static assets.
  • src/config/: Rollup, Astro, PostCSS, and asset-build configuration.
  • tests/unit/: unit tests and shared setup; tests/a11y.mjs checks built pages.
  • dist/ and src/html/public/: generated output. Edit source files; exclude compiled dist/ changes from contributor PRs.

Build, Test, and Development Commands

Use Node.js 22.22.3+, 24.16.0+, or 26.3.0+ and npm 10+. Install with npm ci.

  • npm start: run asset watchers and the Astro dev server at http://localhost:3000.
  • npm run build: clean and rebuild CSS, JavaScript, declarations, assets, and demo/documentation HTML.
  • npm run css / npm run js: rebuild styles or JavaScript independently.
  • npm run lint: check ESLint, Stylelint, Astro, and lockfile rules.
  • npm test: run Vitest once; npm run test-watch enables watch mode.
  • npm run production: clean, lint, test, compile, and check local gzip budgets.

Coding Style & Naming Conventions

Use two-space indentation, UTF-8, LF endings, and final newlines as defined in .editorconfig. Follow neighboring code and repository ESLint/Stylelint rules. Prettier is configured with single quotes, semicolons, and a 100-character line width; Astro has its own configuration.

Use strict TypeScript, PascalCase component classes, camelCase methods, and kebab-case component filenames such as card-widget.ts. Prefix Sass partials with _, and follow existing Bootstrap-compatible selectors and data-lte-* attributes.

Testing Guidelines

Add behavior and regression tests as tests/unit/<component>.test.ts. Vitest uses happy-dom and tests/unit/setup.ts; no numeric coverage threshold is configured. Cover DOM state, events, and cleanup where relevant.

For UI changes, build first, then run npm run test-a11y and npm run test-browser. Install Chromium with npx playwright install chromium; set BROWSERS=chromium,firefox,webkit for all engines. Serious and critical axe violations fail the check. Review responsive layouts, dark mode, and RTL manually.

Commit & Pull Request Guidelines

Follow the prevalent type(scope): summary commit format, for example fix(header): prevent horizontal overflow; common types include feat, fix, docs, and chore.

Branch from and target master. Keep PRs focused, describe the behavior change, link relevant issues, and report validation commands/results. Include screenshots for visual changes. Follow .github/CONTRIBUTING.md and ensure third-party contributions are appropriately licensed.


File: CHANGELOG.md

Changelog

All notable changes to AdminLTE will be documented in this file.

The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.

[4.10.1] - 2026-10-08

Changed

  • Refresh build, lint, formatting, and testing dependencies to the latest compatible stable releases, including Astro 7.3.7, Rollup 4.64.2, Playwright 1.64.0, Vitest 5.0.3, Sass 1.105.1, and ESLint 10.12.0. Replace npm-run-all with the maintained npm-run-all2 and declare the previously implicit unist-util-visit dependency.
  • Update pinned demo and integration libraries, including FullCalendar 7.1.1, Tabulator 6.6.1, OverlayScrollbars 2.16.0, jsVectorMap 1.7.0, and Tom Select 2.6.2. Migrate FullCalendar's stylesheet, Bootstrap theme, draggable, and event-colour APIs, and refresh existing CDN integrity hashes.
  • Building from source requires Node.js 22.22.3+, 24.16.0+, or 26.3.0+ and npm 10+, declared through devEngines; the published package retains its existing Node.js engine requirement. TypeScript remains on 6.0.3 because the latest Astro checker and TypeScript ESLint releases do not yet support TypeScript 7.

Fixed

  • Synchronize every fullscreen control, including when initializing a layout already in fullscreen, without changing public events or component APIs. Scope icon updates to fullscreen controls so they do not interfere with card-maximize buttons.
  • Prevent shared-header overflow at tablet widths, preserve local dates in calendar events, and tolerate an unavailable OverlayScrollbars CDN script.
  • Improve demo filter labels, grid semantics, product-link names, calendar accessible names, and light/dark contrast. Dark-mode outline buttons and documentation links now use theme-aware colour tokens; fixed palette swatches retain a white demonstration background.
  • Check Astro source pages explicitly, fix previously hidden palette typing errors, and repair the ESM Prettier configuration and Unicorn recommended preset.

Added

  • Playwright checks for every built HTML page, responsive light/dark layouts, RTL, and key plugin interactions, with Chromium, Firefox, and WebKit CI jobs.
  • Node.js 22/24/26 unit-test CI, light/dark accessibility CI, and fullscreen regression tests. Missing Playwright now fails accessibility checks rather than silently skipping them.
  • npm run test-size enforces all existing gzip budgets locally without an external reporting service; npm run production uses this check. Bundlewatch remains available for CI reporting.

Security

  • Refresh security overrides, including a patched PostCSS selector parser. Production dependencies audit clean. Development tooling still includes [email protected], affected by GHSA-vfj7-8cjw-p6xm; no patched release is available. Its twenty propagated npm audit findings remain unresolved rather than downgrading the current toolchain.

[4.10.0] - 2026-10-01

Changed

  • The demo charts are drawn with Chart.js (MIT) instead of ApexCharts. From 5.2.0 ApexCharts is published under its own commercial "ApexCharts License" rather than MIT, and a template that thousands of projects copy into their products cannot carry that on to them. Every chart on Dashboard v1, v2 and v3 and on the charts showcase is rebuilt on Chart.js 4.5.1 with the same data, colours and sizes: area and line charts (gradient fills), grouped columns, doughnuts, the radial bar (concentric doughnut rings with the average in the middle), the mixed bar-and-line chart with two value axes, and the dashboard sparklines. Chart.js is loaded from jsDelivr with an SRI hash, only on the pages that draw charts, exactly as ApexCharts was β€” the admin-lte npm package has never depended on a chart library and still does not. Gzipped chart JavaScript per charted page drops from 131.5 kB (ApexCharts 3.37.1 JS + CSS) to 72.8 kB (Chart.js 4.5.1 plus the 2.3 kB inline theme preset below).

    The showcase page is now charts/chartjs.html and the sidebar entry reads "Chart.js". charts/apexcharts.html stays as a redirect so existing links keep working.

  • One chart theme preset, following the colour mode live. The charted pages include a short preset (src/html/components/_chartjs.astro) that sets Chart.defaults from AdminLTE's CSS variables β€” body font, secondary text for ticks, 8% gridlines with no vertical grid on category axes, rounded bars, a tooltip and legend styled like a card β€” and restyles every chart on the page when <html data-bs-theme> changes (the ColorMode toggle, the OS in auto mode, or your own code) or when the web font finishes loading. Under <html dir="rtl"> legends and tooltips are laid out right-to-left. The ApexCharts colour-mode helper that every demo page carried in its shared scripts (#6105) is gone with it, so the 85 pages without charts are 1.6 kB lighter too. The dark-mode charts recipe, the Recommended Integrations, Migration and RTL docs are updated to match.

    One Chart.js detail worth knowing if you adapt the preset: Chart.js copies the scale defaults into each chart's config when the chart is created, so changing Chart.defaults and calling chart.update() leaves existing axes in the old colours. The preset drops those copied colours before it updates; see the comment in the component.

Fixed

  • The brand logo sat 4px left of centre in the collapsed mini sidebar. .brand-text keeps its base margin-left: .5rem while .sidebar-mini.sidebar-collapse shrinks it to max-width: 0, so 8px of margin on a zero-width element pushed the logo off the rail's centre line. The margin is now cleared in that state β€” the hover rule that expands the rail already re-declared margin-left: .5rem, which is what the collapsed state should have mirrored all along (#6114, by @dfsmania).

Maintenance

  • Dev dependencies refreshed β€” eslint 10.8.1 -> 10.9.1, eslint-plugin-unicorn 73 -> 74, @typescript-eslint/* and typescript-eslint 8.67.0 -> 8.69.0, astro 7.2.4 -> 7.2.10, @astrojs/mdx 7.0.7 -> 8.0.0, @astrojs/markdown-remark 7.2.4 -> 7.3.0, rollup 4.62.4 -> 4.63.1, terser 5.50.0 -> 5.51.2, happy-dom 20.11.6 -> 20.12.0, sass 1.103.0 -> 1.103.1, globals 17.11.0 -> 17.12.0. Every one of the 114 built files in dist/ is byte-identical afterwards, so this changes nothing for consumers.

    @astrojs/mdx 8.0.0 is a major but not an Astro 8 requirement β€” it peers astro@^7.2.6 β€” and it flattens the tree rather than growing it: four nested duplicates of @astrojs/markdown-remark and @astrojs/internal-helpers go away and @astrojs/markdown-satteri is hoisted to the top level. npm ci from a clean checkout resolves with no peer warnings.

  • TypeScript stays on 6.0.3. 7.0.2 is out and Dependabot proposes it monthly, but two peers still refuse it: @astrojs/[email protected] (the latest) declares typescript@^5.0.0 || ^6.0.0, and [email protected] declares >=4.8.4 <6.1.0. The upgrade fails at npm ci with ERESOLVE, so it cannot land until both move.

[4.9.1] - 2026-08-26

Fixed

  • A page whose content ended near a sheet boundary still printed one blank sheet. .app-main carries padding-bottom: $grid-gutter-width * .5 β€” breathing room above the fold on screen, trailing whitespace on paper. 12px of it was enough to push a page that otherwise ended 1px inside the second sheet onto a third, empty one; UI/ribbons.html was the case in the demo set, printing three sheets for two sheets of content. The padding is now cleared in print, along with the negative margin-bottom on .app-content-bottom-area that exists only to cancel it. One page of the 90 in dist/ changes page count; nothing else moves (#6113, reported by @johnnyq).

[4.9.0] - 2026-08-26

Changed

  • A printed page is a document again β€” the header, sidebar and footer no longer print. v3 left all three off the paper; v4 printed them, and the result on a real document was the invoice page in the report: 200px of every sheet spent on a menu the reader cannot click, the invoice squeezed into what was left (its own table wrapping "Pro plan subscription" across three lines), and the sidebar's 85 menu items running past the foot of the first sheet β€” so a one-page invoice printed as two pages, the second one all but empty. The content now gets the full width of the paper. Print the whole shell again with data-lte-print="app" on <html> or ``, or bring back one piece of it with Bootstrap's own utilities β€” .d-print-flex for the header, which is a navbar, .d-print-block for the sidebar and footer. .d-print-none still hides anything you don't want on paper, in either mode (#6113, reported by @johnnyq).

    data-lte-print is now matched per token, so data-lte-print="plain app" sets both. data-lte-print="plain" is unaffected.

Fixed

  • The layout kept its viewport-sized dimensions in print. .app-wrapper carries min-height: 100vh and max-width: 100vw, and neither was cleared for print even though the print block deliberately rewrites that same element's grid. Paper has no viewport to fill: the wrapper reserved a full sheet however short the page was, which is why the printed invoice put its footer at the foot of a sheet of empty grey with the content in the top third, and why anything following the wrapper opened a new page. Both are now reset in print, together with the max-width: 100vw on the header, main and footer (#6113, reported by @johnnyq).

  • The print block set display on elements it did not need to. Forcing display: block on .app-sidebar was what made .d-print-none unable to hide it before 4.6.0, and the same shape would have made .d-print-* unable to show it now. Printing the shell is a matter of not hiding it, so the rules that hide it are simply scoped out instead, and nothing in the print block forces a display value on the header, sidebar or footer any more.

[4.8.5] - 2026-08-20

Fixed

  • npm run production printed six Sass deprecation warnings. The palette sheets used the ternary if() in three places β€” lte-needs-aa() in colors/_variables.scss and the text-colour pick in colors/_primary.scss, twice β€” and Sass 1.95.0 deprecated that syntax in favour of the CSS if(). Both adminlte-colors.scss and adminlte-colors-v3.scss pull those files in, hence six warnings for three call sites. They are now plain @if statements rather than Sass's suggested if(sass(...): ...; else: ...), which would need Sass >= 1.95 of anyone compiling AdminLTE from source. Compiled output is byte-identical (#6111, reported by @lfiorini).

    For the record, Bootstrap's own if() calls warn too β€” 82 of them across bootstrap/scss β€” but css-compile already passes --quiet-deps, so nothing under node_modules reaches the console. Every warning in that report was AdminLTE's own.

Maintenance

  • Dev dependencies refreshed β€” sass 1.102.0 -> 1.103.0, astro 7.2.3 -> 7.2.4, @astrojs/mdx 7.0.6 -> 7.0.7, happy-dom 20.11.2 -> 20.11.6. All within their current majors; dist/ is byte-identical, so this changes nothing for consumers.
  • The docs build moved off Astro's deprecated markdown.rehypePlugins. Astro 7.2.4 deprecates the top-level remarkPlugins / rehypePlugins / remarkRehype keys in favour of an explicit markdown.processor, so the bump above added a fresh warning to the very command #6111 was about. The responsive-tables plugin now goes through unified() from @astrojs/markdown-remark β€” the same remark/rehype pipeline, now a direct dev dependency. All 90 built pages are byte-identical, .table-responsive wrapping included.
  • nanoid bumped to 3.3.18 in the lockfile β€” GHSA-2v37-7h3g-55p8 (high), a transitive dev-only dependency where a custom generator could loop forever on size: 0. npm audit now reports zero vulnerabilities; nothing shipped in dist/ was ever affected.
  • TypeScript stays on 6.0.3. 7.0.2 is out, but typescript-eslint still declares peerDependencies.typescript: ">=4.8.4 <6.1.0" and @astrojs/check accepts ^5 || ^6, so the toolchain still cannot move.

[4.8.4] - 2026-08-19

Fixed

  • Reference tables on the docs pages pushed the page sideways on a phone. Markdown pipe-tables render as a bare `` with no scroll container, so anything wider than three columns overflowed the viewport β€” 265px of it on the Colors page. They are now wrapped in .table-responsive with a tab stop, exactly like the hand-written tables next to them, so the table scrolls inside the column and stays keyboard-reachable. Desktop layout is unchanged.

[4.8.3] - 2026-08-19

Fixed

  • Every page scrolled sideways on a phone. At 390px the header's right-hand item row (messages, notifications, fullscreen, user menu) is a little wider than the viewport at Bootstrap's default 1rem nav-link padding, so document.scrollWidth came out 22px past clientWidth on all 90 demo pages β€” a horizontal scrollbar on every screen. Below sm the header now halves --bs-navbar-nav-link-padding-x; from sm up nothing changes (the user menu keeps its 182px width and its label). Measured 412/390 before, 390/390 after, desktop unaffected.

    Worth noting for anyone who checks this themselves: measure scrollWidth > clientWidth, not > window.innerWidth β€” the layout viewport grows to match the overflow, so the innerWidth comparison silently passes.

[4.8.2] - 2026-08-19

Fixed

  • The documentation's install snippets were being served as [email protected]. Cloudflare's Email Obfuscation treats any package@version in a text node as an email address and rewrites it as a mailto link β€” so every version-pinned snippet on the live docs pages was unusable: 31 of them on Integrations (Flatpickr, Tom Select, Quill, Chart.js …), 8 each on Introduction and Deployment, 7 on Getting Started. The docs pages now carry `` markers, the same fix docs.adminlte.io already used.

Changed

  • The docs' install snippets are stamped from package.json. Introduction, Getting Started, Deployment and Colors still pinned [email protected], eight releases after that was current. sync-version.mjs now stamps [email protected] in the docs sources alongside the SCSS/TS banners, so npm version keeps them honest. A deliberate floating admin-lte@4 (the README quick start) is left alone.

Maintenance

  • Dev dependencies refreshed β€” astro 7.1.6 β†’ 7.2.3, @astrojs/mdx 7.0.5 β†’ 7.0.6, eslint 10.8.0 β†’ 10.8.1, the typescript-eslint trio 8.66.0 β†’ 8.67.0, vitest 4.1.10 β†’ 4.1.11, happy-dom 20.11.1 β†’ 20.11.2, axe-core 4.12.1 β†’ 4.13.0, terser 5.49.2 β†’ 5.50.0, postcss 8.5.25 β†’ 8.5.26, globals 17.9.0 β†’ 17.11.0, concurrently 10.0.4 β†’ 10.0.5, lockfile-lint 5.0.0 β†’ 5.0.1. All within their current majors; dist/ is byte-identical, so this changes nothing for consumers.
  • TypeScript stays on 6.0.3. 7.0.2 is out, but typescript-eslint still declares peerDependencies.typescript: ">=4.8.4 <6.1.0" and @astrojs/check accepts ^5 || ^6, so the toolchain cannot move yet. It will be picked up when those two allow it.

[4.8.1] - 2026-08-19

Fixed

  • Pagination lost its focus ring under data-lte-primary. Both palette sheets shipped a literal $pagination-focus-box-shadow-width in their compiled output: Sass does not evaluate $variables inside a custom property value β€” it emits them verbatim rather than failing β€” so the typo compiled cleanly, reached the browser as text, and made the declaration invalid. --bs-pagination-focus-box-shadow then resolved to nothing and keyboard focus on .page-link became invisible, a keyboard-accessibility regression introduced in 4.6.0. The value is now built from Bootstrap's focus-ring tokens (the variable it named never existed; the right one is $focus-ring-width) and follows the theme colour. A unit test now scans the compiled output of all three sheets for unresolved Sass variables, since compilation cannot catch this class of typo (#6109, reported by @johnnyq).

[4.8.0] - 2026-08-19

Added

  • data-lte-contrast="aa" β€” WCAG AA text on the AdminLTE 3 palette, without changing a single colour. The v3 sheet reproduces v3's YIQ text rule faithfully, and that rule assigns white text to eight colours that do not reach 4.5:1 for normal text: teal (2.13:1), cyan (3.04), green (3.13), olive (3.51), lightblue (3.67), fuchsia (3.79), pink (3.82) and blue (3.98). The attribute β€” on <html>, `` or any container β€” flips exactly those eight to the ink that passes (5.28:1 to 9.86:1), leaving every background byte-identical, and 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 data-lte-primary produces. It has to be true black: AdminLTE 3's own dark ink #1f2d3d clears 4.5:1 on only two of the eight, so the colours that already carry dark text (lime, orange, yellow) keep #1f2d3d and are left alone. Rules are emitted per colour only where the palette's own rule falls short, so the designed palette β€” every colour AA with white by construction β€” emits nothing and the switch costs it nothing. UI β†’ Colors and Theme Customize gained a toggle that also re-pairs a flipped sidebar or header with data-bs-theme="light" so its nav links follow (#6110, reported by @johnnyq).

Fixed

  • data-lte-primary gave Bootstrap's info white text on the v3 sheet. The primary presets applied the active sheet's contrast rule to Bootstrap's own theme colours too, and v3's YIQ test puts #0dcaf0 at 149.8 against a threshold of 150 β€” one tenth of a point from dark text β€” so data-lte-primary="info" produced white on cyan at 1.96:1 while .btn-info on the same page stayed black. Bootstrap's theme colours now keep Bootstrap's own contrast rule; the palette colours keep the sheet's.

[4.7.0] - 2026-08-18

Added

  • data-lte-print="plain" β€” print a page as a document, not as a copy of a screen. Two rules in the print stylesheet restyle content unconditionally: the target of every external link is printed after it (a[href^="http"]::after), and buttons are outlined in black. They are the HTML5 Boilerplate conventions and they suit printing a web page, but they are wrong for an invoice, quote or work order that goes to a customer β€” Pay online (https://pay.example.com/inv/12345?token=…) is not what anyone wants on paper. The attribute turns both off, on <html>, `` or any single container, while text stays black on white so nothing becomes unreadable. Note that .d-print-none could not have served as the opt-out: it hides the element, and these rules restyle elements that are meant to print (#6108, requested by @johnnyq). Documented as the β€œPrinting a page as a document” recipe.

Fixed

  • Every sidebar item printed inside a black box. The print rule that outlines buttons also listed .nav-link, and the sidebar prints by design (since 4.0.0), so a printed page came out with a black rectangle around all 85 menu items. The outline now applies to .btn only; nav links keep the ink-saving black-on-transparent treatment and print as ordinary text (#6108).
  • The FAQ answer for β€œHow do I change the primary colour?” recommended overriding --bs-primary on :root β€” the one approach that leaves .btn-primary blue, which is what led to #6107. It now gives the two that work: $primary in Sass, or data-lte-primary with the palette sheet.

[4.6.0] - 2026-08-18

Added

  • Make any palette colour Bootstrap's primary β€” data-lte-primary. Bootstrap compiles $primary into its components, so .btn-primary ships with --bs-btn-bg: #0d6efd rather than var(--bs-primary): overriding the token repainted the utilities and left every button, pagination, checkbox and focus ring blue, which is why a coloured skin looked half-applied. Both colour sheets now rewire those components to variables and point them at a colour with one attribute β€” <html lang="en" data-lte-primary="teal"> β€” covering .btn-primary / .btn-outline-primary (hover, active and disabled shaded exactly as Bootstrap shades its own), .btn-link, links and .link-primary, the --bs-primary token trio, .nav-pills, .pagination, .progress-bar, .list-group-item.active, .dropdown-item.active, .accordion, .form-control / .form-select focus, checkboxes, radios, switches, .form-range, --bs-focus-ring-color, and AdminLTE's .card-primary, .direct-chat-primary and .toast-primary β€” in both colour modes, with Bootstrap's dark-mode link tint. Nothing changes without the attribute, and it works on any container, so one page can carry more than one primary. Bootstrap's own theme colours are presets too (data-lte-primary="danger"), a brand colour outside the palette needs only --bs-primary (hover shades are then derived with color-mix() where supported), and $lte-palette-custom: ("brand": #hex) generates a full preset in Sass. Every skin preset now names the primary that goes with it, so applying a skin on UI β†’ Colors or Theme Customize recolours the buttons too; both pages gained a picker and a live preview, and the Colors docs page a section (#6107, requested by @johnnyq).

Fixed

  • d-print-none did nothing on the sidebar and the app wrapper. The print layout fix from 4.0.0 set display: grid !important on .app-wrapper and display: block !important on .app-sidebar, and β€” being later in the cascade and equally !important β€” beat Bootstrap's .d-print-none, so those two elements could not be hidden for printing. Both rules are now scoped with :not(.d-print-none): the print layout still applies to the standard layout, and the display utilities win where a page asks them to (#6106, reported by @johnnyq).
  • The skip link went invisible on hover. .skip-link:hover painted var(--bs-primary-emphasis), which Bootstrap does not define (it emits --bs-primary-text-emphasis), so the background computed to transparent and left white text on the page background. It now uses the emphasis token β€” dark in light mode, light in dark mode β€” with the body background for the text.

Maintenance

  • Computed colours in both palette sheets are emitted as hex instead of Sass's rgb(5.647%, 40.784%, 39.216%) (cleancss leaves values inside custom properties alone), which pays for a good part of the new feature: adminlte-colors.css is β‰ˆ 8 kB gzipped and adminlte-colors-v3.css β‰ˆ 9.3 kB. adminlte.css is unchanged.
  • npm run palette -- --check and the unit tests also verify that every skin's primary exists in its own palette and that both sheets emit the rewiring rules and a preset per colour.

[4.5.0] - 2026-08-18

Added

  • The AdminLTE 3 palette, as it was β€” dist/css/adminlte-colors-v3.css. A second opt-in sheet for everyone who preferred the original colours: the 18 AdminLTE 3 colours (lightblue, navy, olive, lime, fuchsia, maroon, blue, indigo, purple, pink, red, orange, yellow, green, teal, cyan, gray, gray-dark) with the values from the 3.2 build and v3's text-colour rule (Bootstrap 4's YIQ test β€” white on lightblue, olive, teal…, dark on lime, orange, yellow), emitting exactly the same class families as the designed palette. Load one sheet or the other; they share class names. Built from the same Sass β€” new $lte-palette-contrast: "wcag" | "yiq" knob (with $lte-palette-yiq-threshold, $lte-palette-yiq-text-dark/-light) β€” so $lte-palette-custom and $lte-palette-aliases work with it too. Twelve AdminLTE 2 skin presets (skin-blue, -black, -purple, -green, -red, -yellow and their -light variants) in v4 terms. UI β†’ Colors and Theme Customize gained a palette switch that swaps the stylesheet live and shows that palette's tiles, presets and pickers; the Colors docs page documents both. npm run palette -- --check and the unit tests keep the v3 map and its metadata in step (#6103, follow-up for @johnnyq).

[4.4.1] - 2026-08-18

Fixed

  • Charts unreadable in dark mode. ApexCharts drew its light-theme tooltip (white background) while the tooltip text inherited the page's dark-mode body colour β€” near-white on white β€” and axis labels stayed dark grey on the dark card, in every browser. Every demo page now carries a small shared helper (_scripts): it sets ApexCharts' global default theme.mode from <html data-bs-theme> before any chart is created, keeps the chart background transparent, and watches the attribute so every chart with a chart.id follows the toggle, the OS preference in auto mode, or your own code. All demo charts got ids. Documented as the β€œCharts that follow dark mode” recipe (#6105, reported by @Yusk03).

[4.4.0] - 2026-08-18

Added

  • Extended colour palette (dist/css/adminlte-colors.css) β€” an opt-in stylesheet (β‰ˆ 5 kB gzipped; adminlte.css is unchanged) that adds fourteen colours β€” orange, amber, olive, teal, sky, indigo, violet, fuchsia, pink, and the chrome neutrals navy, steel, slate, graphite, midnight β€” as --bs-* tokens plus every family Bootstrap generates for its own theme colours: .bg-*, .text-bg-*, .text-*, .border-*, .link-*, .bg-gradient-* (also for the eight theme colours, as in v3), .card-* / .card-outline, .callout-*, .direct-chat-*, with the subtle/emphasis tokens for dark mode and a hover shade for .btn.bg-*. The palette is generated, not picked: in OKLCH, each chromatic colour is the lightest value whose white text still reaches 4.6:1 (WCAG AA), hues sit β‰₯ 17Β° from every Bootstrap theme colour and β‰₯ 20Β° from each other, chroma is capped at parity with primary, and the five neutrals are cool-tinted and sit at the lightness steps popular admin kits use for sidebars. scripts/palette.mjs regenerates it (npm run palette) and checks it (--check); a unit test keeps the Sass map and the demo data in step. Add your own with $lte-palette-custom: ("brand": #hex), replace the set with $lte-palette, restore v3 names with $lte-palette-aliases: ("lightblue": "sky", "maroon": "pink"). Documented on the new Colors page (docs/colors.html) β€” palette, class families, 17 skin presets in four families (light, semi-dark, full dark, coloured & gradient) each with a layout thumbnail, widget-set presets, brand-colour recipe, design rules with a hue/chroma plot β€” and demonstrated on UI β†’ Colors (UI/colors.html), which recolours its own sidebar and header live from thumbnail presets, shows the four dashboard sets and every component, and has a brand-colour checker that reports contrast, proposes a same-hue value that passes, and prints the Sass snippet (#6103, requested by @johnnyq).
  • Header search and sidebar filter restyled β€” one shared treatment (mixins/_search-field.scss): a quiet pill with the icon inside as the submit button, coloured only from theme-relative tokens (--bs-emphasis-color-rgb, --bs-body-color), so it reads as soft glass on any coloured or dark header / sidebar and soft grey on a white one, instead of a boxed .input-group with an outline button that turned near-black under data-bs-theme="dark". The header field widens slightly on focus; below md an icon links to the search page. New variables $lte-search-field-* (width, focus width, font size, padding, icon space); the Main Header docs snippet, which still showed v3's inert data-widget="navbar-search", now shows the real markup.
  • Theme Customize (generate/theme.html) offers the palette in its sidebar / navbar / footer pickers under "Extended palette", gained a preset row with the same thumbnails, reflects the page's current state on load, and defaults the colour mode to dark when a palette colour is picked; its three duplicated scripts are one controller.
  • The Migration and Customization pages point to the palette; the migration guide maps every AdminLTE 3 colour and skin class to its v4 form.

Fixed

  • Sidebar taller than the viewport with the sidebar filter in place. .sidebar-wrapper was sized with a hard-coded 100vh βˆ’ header height, which assumed only the brand sat above it; the SidebarSearch field added in 4.3.0 sits between the brand and the wrapper (on purpose, so it stays put while the menu scrolls), so the wrapper overflowed the sidebar by exactly the filter's height and the last menu items were cut off. .app-sidebar is now a flex column: brand and filter keep their size (flex: 0 0 auto), .sidebar-wrapper fills the rest (flex: 1 1 auto; min-height: 0) and scrolls β€” in fixed layouts, the fixed-header-only case, and the off-canvas sidebar. Anything else placed above or below the menu now sizes correctly too, without a height calc (#6104, reported by @dfsmania).

[4.3.1] - 2026-08-10

Fixed

  • Ribbons left the card corner bare. The banner was centred on its clipping square, but the square's centre is the point furthest from the corner β€” so the banner sat too far into the card, left a large empty triangle at the corner it is supposed to cover, and had its label cut off by the clip. The banner's centre now sits on the corner's bisector at ~46% of the square's side, the proportion that leaves the label room and fills the corner, and the width is the chord across the corner rather than the square's diagonal. RTL mirrors as before.
  • The ribbons demo uses short labels and states the constraint: the banner is clipped to the corner, so only about 64px of it is readable at the default size. .ribbon-lg and .ribbon-xl give more room. Documented on the Miscellaneous components page.

[4.3.0] - 2026-08-10

Added

  • Gallery page (pages/gallery.html) β€” a filterable media library: responsive tile grid, per-item action menus, a live result count and an empty state. It carries no image library at all β€” the tiles are a Bootstrap .ratio box plus .object-fit-cover, and the category filter is ~25 lines of page-local script, so nothing is added to adminlte.js and no CDN dependency is imposed on anyone who just wants the grid. A lightbox stays deliberately out of scope; it is the part that needs a dependency.
  • Search results page (pages/search-results.html) β€” typed results (page / user / file / project, each with its own icon and badge) over a tabbed type filter, plus a refine panel, related searches, pagination and a worked "no results" state. The navbar search now posts to it with name="q" instead of action="#", so the search added alongside SidebarSearch has a real destination.
  • Language menu in the app header β€” a bi-translate dropdown listing five locales with the current one marked via .active + aria-current, and hreflang on each item. It is markup only, by design; the new "A language switcher wired to a real locale" recipe in the docs covers the four things that separate a working switcher from set dressing β€” real per-locale links rather than # plus a click handler, the locale living in the URL or a cookie rather than localStorage (the server has to know it before it renders), marking the current language for assistive tech, and serving adminlte.rtl.css alongside dir="rtl" when switching to an RTL locale.
  • SidebarSearch plugin β€” a filter field above the sidebar menu (data-lte-toggle="sidebar-search" on an input, optional data-lte-target for the menu). Typing hides non-matching entries and expands whatever submenu holds a match; a group whose own name matches keeps its entire subtree, so expanding it is never a dead end. Clearing the field β€” ESC, the native clear button, or clear() β€” restores every entry and each submenu's previous open state, including the inline display Treeview's slide animation leaves behind. Entries are hidden with the hidden attribute rather than a class, so they leave the accessibility tree too. Fires filtered.lte.sidebar-search with { query, matches }; both listeners are delegated on document, so a sidebar rendered after load needs no re-initialisation. Wired into every demo and docs page, and documented at docs/javascript/sidebar-search.html.
  • Navbar search in the app header β€” a role="search" form with a visually-hidden label, shown from the md breakpoint up, posting to the new search results page.
  • Ribbons (.ribbon-wrapper + .ribbon, sizes .ribbon-lg / .ribbon-xl) β€” the corner banner from v3, rebuilt. The wrapper clips the banner and the banner is centred on it before rotating, so the whole geometry follows one custom property (--lte-ribbon-size) and stays correct at every scale; the clip inherits the parent's corner radius, so it follows a rounded card instead of cutting a square out of it. Colour comes from a .text-bg-* utility and long labels truncate rather than breaking the shape. rtlcss mirrors both the corner and the rotation, so RTL needs no extra markup. New demo page at UI/ribbons.html.
  • Social and post widgets (.user-block, .post, .widget-user, .widget-user-2, .description-block) β€” the profile cards and activity-feed blocks from v3, keeping their v3 class names so migrating markup works unchanged, but laid out with grid and flex rather than floats and coloured with Bootstrap utilities. .user-block renders avatar, name and meta from flat markup with no wrapper element. New demo page at widgets/social.html.
  • Nav tabs & pills, modal and offcanvas sections on UI/general.html β€” the three Bootstrap components the components showcase did not yet cover.
  • Card and Miscellaneous documentation pages (docs/components/card.html, docs/components/miscellaneous.html) β€” between them they document every AdminLTE component that ships CSS but had no reference until now: cards (anatomy, colour variants, states, tabbed cards, Sass variables), callouts, ribbons, small box, info box, timeline, the social/post widgets, progress-bar sizes, themed toasts and the image-size utilities.
  • Select2 compatibility theme (dist/css/adminlte-select2.css) β€” an opt-in, CSS-only sheet for projects that can't drop Select2 yet. AdminLTE still bundles neither Select2 nor jQuery and nothing is added to adminlte.css; load this one extra stylesheet after select2.css and a Select2 you bring yourself inherits Bootstrap 5.3's form metrics (.form-select-sm / -lg on the original <select> are honoured), focus ring, .is-invalid / .is-valid states, input-group corner rounding, badge-style multi-select pills, and data-bs-theme dark mode β€” including the body-appended dropdown. It also raises the dropdown above .modal, which removes the usual need for dropdownParent. Source lives in src/scss/compat/_select2.scss for anyone compiling their own Sass; RTL and minified variants are generated alongside the rest of the CSS (#6097, requested by @saddam2512).
  • Third-party plugin replacement table on the Migration page: every jQuery widget AdminLTE 3 bundled (Select2, DataTables, Summernote, iCheck, bootstrap-datepicker, jQuery Knob, Mapael, …) mapped to its vanilla-JS successor. The old plugin names were absent from the v4 docs entirely, so anyone searching for the plugin they knew found nothing and concluded it was unsupported.
  • Select2 β†’ Tom Select API mapping, on both the Advanced Elements demo page and the Integrations docs page: 19 options, methods and events translated one-to-one (ajax β†’ load + loadThrottle, templateResult β†’ render.option, select2("destroy") β†’ ts.destroy(), and so on).
  • jsVectorMap documented on the Integrations page as the dependency-free successor to AdminLTE 3's jQuery Mapael.
  • New FAQ entry, "Does AdminLTE 4 support Select2?", plus a pointer from the existing jQuery question to the replacement table. The Deployment asset table and the RTL page now list the optional Select2 sheet alongside the core builds, and the README's breaking-changes list links the replacement table.

Changed

  • Removed the dead src/html/components/docs/faq.mdx. The docs FAQ has been rendered from the typed sections array in src/html/pages/docs/faq.astro since the accordion rewrite, and nothing imported the .mdx any more β€” but two later commits kept editing it, so those edits silently did nothing. It is the only docs .mdx with no importer; every other one is rendered by its matching pages/docs/*.astro.
  • The remote-data Tom Select demo in forms/advanced.html is now the real pattern, not a setTimeout sketch: request debouncing via loadThrottle, in-flight cancellation via AbortController, and page-by-page loading through the virtual_scroll plugin against a 72-record stub whose { results, next } response shape matches a real endpoint β€” swap the stub body for a single fetch() and it works unchanged. The single select also gained the clear_button plugin.
  • The axe gate covers 16 pages (from 10), adding UI/general.html, UI/ribbons.html, widgets/social.html, pages/gallery.html, pages/search-results.html and docs/components/miscellaneous.html. Unit tests are up to 62 (from 51). The two new pages needed three Bootstrap-default contrast fixes the gate caught: .text-secondary under 4.5:1 on the tertiary page ground, the nav-tab link blue under it on the same ground (the tabs moved onto the card surface, which is the documented .card-tabs shape), and .btn-outline-secondary under it on a card footer.
  • Outline buttons on UI/general.html are demonstrated where they are legible. The warning, info and light outline variants draw their text in a light colour that falls below 4.5:1 on a light surface β€” adding the page to the axe gate surfaced it. They now sit on a dark panel, with a note saying why, which is also the guidance Bootstrap gives for them.
  • Bundlewatch budgets raised for the ribbon, widget and SidebarSearch additions: adminlte.min.css 42.5 β†’ 43.5 kB and adminlte.min.js 7.5 β†’ 8.5 kB gzip (and the matching RTL/ESM/unminified entries).

Fixed

  • Dashboard v2 linked to a page that does not exist. Seven order-number links in index2.html pointed at pages/examples/invoice.html; the invoice page is at pages/invoice.html. Caught by a new link-resolution pass over every built page.
  • #navigation no longer resolves to the app header. ensureSkipTargets() looked its two skip-link targets up with a single selector list each (#navigation, nav, [role="navigation"]), but a selector list resolves in document order rather than by which selector matched β€” so on every demo page, where the header <nav> precedes the sidebar menu that already carries id="navigation", the header was stamped with the id a second time. That left a duplicate id in the document, pointed the "Skip to navigation" link at the header instead of the menu, and silently redirected any application code doing document.querySelector("#navigation") to the wrong element. An element the page already gave the id to now always wins.

[4.2.0] - 2026-08-06

Added

  • Advanced form pages: forms/advanced.html demonstrates the recommended Tom Select and Flatpickr integrations (searchable single select, tag-style multi-select, option groups loaded on demand; date, date-time, range, and time pickers), and forms/editors.html demonstrates Quill 2 in both the snow (toolbar) and bubble (inline) themes. CDN assets are pinned to the versions recommended on the docs Integrations page and carry SRI hashes. Both pages are linked from the sidebar's Forms menu.
  • Top-nav layout demo: layout/top-nav.html β€” a sidebar-less page whose primary navigation lives in the app-header as a plain Bootstrap navbar-expand-lg (collapse toggler on mobile, no PushMenu). No new CSS was needed: without an .app-sidebar in the markup, the app-wrapper grid's auto sidebar column collapses to zero width on its own.
  • Color mode: server-rendered themes, and an opt-out for applications with their own theming. ColorMode now resolves the theme from the visitor's stored choice, then a theme the page declared itself in <html data-bs-theme="light|dark">, then the OS preference β€” so a theme rendered server-side from a cookie or a user record is no longer overwritten on load, and no longer lost the first time the OS preference changes. Applications that manage data-bs-theme themselves β€” including anyone using a custom Bootstrap theme name, which ColorMode cannot resolve β€” turn the module off entirely with <html data-lte-color-mode="off">: nothing is written on load, on a [data-bs-theme-value] toggle click, or when the OS preference changes, and the pre-paint snippet in <head> honours it too (#6084, reported by @cytech; #6093, thanks @dfsmania). The snippet now flags the values it resolves itself with data-lte-theme-resolved, so a computed theme is never mistaken for one the page declared.
  • initialize() / teardown() lifecycle API for client-side rendered layouts: frameworks that build the layout after DOMContentLoaded into a persistent `` (GWT and similar widget toolkits) can call adminlte.initialize() once the layout is attached β€” it tears down the previous cycle's listeners and re-runs every plugin's initialisation against the current DOM, and is safe to call repeatedly (#6083, thanks @themarioga). teardown() is the inverse, for apps that unmount the layout. Re-initialisation was hardened along the way: the accessibility module's per-field invalid listeners no longer stack across cycles, an initialize() call made while the document is still loading no longer suppresses the initial DOMContentLoaded pass, and calling initialize() from inside a lifecycle callback no longer recurses.

Changed

  • Dev dependencies refreshed to their latest releases, including two majors: eslint-plugin-astro 2 β†’ 3 and eslint-plugin-unicorn 72 β†’ 73, whose new single-line-block-comment-style rule prompted converting four one-line banner comments in src/ts/util/index.ts to line comments. Also Astro 7.1.6, ESLint 10.8, typescript-eslint 8.66, Sass 1.102, Rollup 4.62.4, Terser 5.49.2, PostCSS 8.5.25, happy-dom 20.11.1, @astrojs/mdx 7.0.5, plus concurrently, fs-extra and globals. TypeScript stays on 6.0.3: 7.0 has shipped, but both typescript-eslint (peer >=4.8.4 <6.1.0) and @astrojs/check (peer ^5 || ^6) still cap it, so installing it fails dependency resolution outright. Nothing in dist/ changed as a result. Supersedes #6085–#6092.
  • Component docstrings corrected in base-component.ts and push-menu.ts (#6082, thanks @dfsmania), the adminlte-docs.scss banner is now version-synced by npm version along with the other source banners, and the README's screenshots were downscaled to WebP for faster cold-cache loads.

Fixed

  • A sidebar rendered collapsed no longer springs open on load: a page shipping `` had that state overwritten by PushMenu's responsive pass during initialisation, so a deliberately collapsed sidebar expanded itself on wide screens. The responsive pass now runs only when the sidebar was not already collapsed in the markup (#6076, thanks @dfsmania).
  • The Color Mode docs page ran two switchers at once: docs/color-mode.html still carried the pre-4.1 inline toggler, which bound its own handlers to the same [data-bs-theme-value] buttons as the bundled module and persisted to a different localStorage key (theme instead of lte-theme). It has been removed β€” the page now uses the shipped ColorMode like every other page.

[4.1.0] - 2026-07-02

Added

  • ESM bundle and TypeScript declarations on npm: dist/js/adminlte.esm.js (+ .min) ships alongside the UMD build, generated .d.ts files ship under dist/js/types/, and package.json gains module, types, and a full exports map (with sass/style conditions and ./dist/* + ./src/scss/* subpaths). import { PushMenu } from "admin-lte" now resolves natively in Vite/webpack and type-checks out of the box β€” previously the package shipped a single minified UMD file with no typings at all.
  • Component lifecycle API (Bootstrap-style): every JS component now has getInstance(element), getOrCreateInstance(element, config?) and dispose(), backed by a per-element WeakMap registry (instances are garbage-collected with their elements β€” Turbo-safe). The data API now uses delegated document-level listeners, so toggles inside content inserted after page load (AJAX partials, Turbo Frames) work without re-initialisation. PushMenu is finally controllable programmatically via PushMenu.getInstance(sidebar).
  • ColorMode module in the bundle: the light/dark/auto switcher (persisted in lte-theme, OS-preference aware, [data-bs-theme-value] data-API, changed.lte.color-mode event) is now part of adminlte.js. Applications no longer need to copy the demo's inline script; the demo pages now use the bundled module. Only the tiny no-flash snippet in <head> remains inline, by design.
  • bootstrap declared as a peer dependency β€” the Sass source imports it, so @use "admin-lte/src/scss/adminlte" now works after a plain npm install admin-lte (npm installs the peer automatically). Documented the required Sass load-path setup.
  • New demo pages: a blank starter page (the most-requested v3 page, absent from v4), a dedicated ApexCharts page with six chart types, and a Users management page (searchable directory table, add-user and delete-confirmation modals, pagination). All linked from the sidebar.
  • Test baseline: a vitest + happy-dom unit suite (30 tests across the component lifecycle, card/treeview/push-menu behavior, ColorMode, and slide animations) wired into npm run production; plus npm run test-a11y β€” an axe-core check over key built demo pages that fails on serious/critical WCAG violations, with a dedicated GitHub workflow.

Fixed

  • First findings of the new axe gate, fixed at the source: breadcrumb links now use the darker link shade (Bootstrap's default blue was 4.26:1 against the content-header's gray background β€” below WCAG AA's 4.5:1), the direct-chat message pane is keyboard-focusable (tabindex="0" + role="log"), and muted footnote text uses text-body-secondary instead of the failing text-secondary.

Changed

  • Component events overhauled (behavior change): all plugin events are now bubbling CustomEvents dispatched on the component's root element (the card, the nav item, the sidebar) β€” previously most were non-bubbling and card events fired on whatever was clicked, including the `` icon. Animated actions gained cancelable "before" events (collapse/expand/remove.lte.card-widget, expand/collapse.lte.treeview, open/collapse.lte.push-menu) and their "after" events (collapsed, expanded, removed, opened, …) now fire when the animation completes, not when it starts. If you listened for card events on the tool buttons themselves, listen on the card or on document instead.
  • Docs styles split out of the production CSS: the documentation/FAQ styling now compiles to a separate adminlte-docs.css, loaded only by the docs pages. Together with the 4.0.4 dedup, adminlte.min.css is down to ~40.4 KB gzip (from 46.7 KB in 4.0.3); bundlewatch budgets tightened accordingly.
  • Vendored Bootstrap variables fork replaced: the 1,766-line _bootstrap-variables.scss copy (which had to be re-synced by hand every Bootstrap release) is gone; AdminLTE's ~10 actual changes now live in a small _bootstrap-overrides.scss loaded before Bootstrap's own variables. Compiled CSS is byte-identical.
  • Sass deprecation policy: the build no longer silences all warnings (--quiet); it silences only dependency warnings and the known @import deprecation (--quiet-deps --silence-deprecation=import), so new deprecations in AdminLTE's own code surface at build time. All deprecated global built-ins (map-get, map-keys) migrated to the sass:map module. The full @use module-system migration is intentionally deferred until Bootstrap ships module-system Sass (Bootstrap 6) β€” Bootstrap 5's partials are designed around @import's shared global namespace and cannot be loaded individually via @use.
  • Rewrote the color-mode docs page around the bundled ColorMode module (the old copy-paste script it showed used a stale storage key); tsconfig.json no longer carries the invalid "root": true option.

[4.0.4] - 2026-07-02

Added

  • Forgot-password example page β€” both login pages have linked forgot-password.html since 4.0.0, but the page didn't exist (dead link in every deployed demo). Now shipped and listed in the sidebar under Examples β€Ί Version 1.
  • Treeview exposes its state to assistive tech: submenu toggle links now carry aria-expanded, stamped on init and kept in sync on open/close.

Fixed

  • npm packaging: the package is now built from a files allowlist instead of the .npmignore denylist. Stray local files can no longer leak into the tarball (4.0.2 shipped an untracked working file this way), and the demo/docs HTML β€” which SECURITY.md advises never to deploy β€” is no longer published to npm. Unpacked size drops from 12.7 MB to 9.0 MB (177 β†’ 95 files). Also declares engines: node >= 20.
  • CSS bundles shipped the docs-site styles twice: _docs.scss was imported from both adminlte.scss and parts/_core.scss, and Sass @import duplicates output β€” ~23 KB of dead weight in each of the four dist stylesheets.
  • Accessibility module keyboard handling:
    • removed the document-edge Tab wrap β€” it acted as a page-level keyboard trap (WCAG 2.1.2), preventing keyboard users from ever tabbing out to the browser chrome
    • arrow keys are no longer intercepted inside inputs, textareas, selects, or contenteditable elements (typing in a navbar search field used to yank focus into the menu), and menu arrow-navigation only engages when focus is actually on a menu item
    • modal focus restore now captures the triggering element on show.bs.modal (capturing on shown stored an element inside the modal, so closing dropped focus to ``); [autofocus] is respected
  • Hotwired Turbo no longer duplicates injected DOM: the skip links, #live-region, and sidebar overlay are now reused when a restored `` snapshot already contains them β€” previously they accumulated one copy per navigation.
  • Card widget: remove() now actually removes the card from the DOM after the animation (it only hid it, so hidden form fields kept submitting); clicking the collapse toggle mid-animation reverses it instead of being swallowed; widget events now dispatch on the toggle button itself rather than a clicked `` icon; minimize() cleans up its inline styles.
  • Slide animations are cancelable: rapid-toggling a treeview or card no longer lets a stale animation timer strip styles mid-flight and desync display state.
  • Treeview accordion guard compared each open item against the parent `` (never true), so open() on an already-open item slid its own menu shut.
  • PushMenu reacts to viewport changes via matchMedia on the actual breakpoint crossing β€” mobile URL-bar/keyboard resizes and same-side width changes no longer re-expand a sidebar the user collapsed; the default breakpoint (991.98) now matches the CSS convention, fixing a 992px off-by-one.
  • Callout variants referenced two custom properties that were never defined, so links and inline code inside callouts never recolored; the user-menu footer used --bs-light-bg, which doesn't exist in Bootstrap 5.3 (now --bs-tertiary-bg).
  • Demo pages: Bootstrap JS CDN pin updated 5.3.7 β†’ 5.3.8 to match the compiled CSS; removed the dead navbar-search button (data-widget="navbar-search" has no implementation in v4); every page now has exactly one <h1> (page titles were <h3>); breadcrumbs are wrapped in <nav aria-label="breadcrumb">; all icon-only buttons (card tools, topbar toggles) have aria-labels; auth forms have real <label>s and <main> landmarks; dated "Google+" copy updated.

Changed

  • ACCESSIBILITY-COMPLIANCE.md rewritten as an accurate accessibility statement β€” what's implemented, what's partial (treeview keyboard pattern, drag-and-drop alternatives, touch-target sizes), and what's on the roadmap β€” replacing the aspirational all-checked WCAG checklist. Demo meta descriptions updated to match.
  • bundlewatch budgets recalibrated: CSS budgets tightened (46 β†’ 44 kB min+gzip) to lock in the docs-dedup win; JS budget raised (5.8 β†’ 6.5 kB) for the behavior fixes above.

[4.0.3] - 2026-07-01

Added

  • Hotwired Turbo / Turbo Drive support: plugins now re-initialise on turbo:load, so PushMenu, TreeView and the other JS components keep working after Turbo swaps the `` on in-app navigation (previously they went dead after the first link click). Each init cycle uses an AbortController whose signal is aborted on turbo:before-render, so the window/document-level listeners are torn down before re-init instead of stacking up on every navigation. (#563, #5890 β€” diagnosed and prototyped by @MarkDaleman in #6058)

Fixed

  • Fullscreen state sync: the fullscreen icons and the maximized/minimized events are now driven by the native fullscreenchange event instead of the request/exit calls. The UI no longer flips when a fullscreen request is denied (permissions policy, missing allowfullscreen, lost user gesture), and it now stays in sync when the user exits with ESC or F11. (builds on @webgo-oss's report in #6055)
  • Accessibility: form inputs lacking both an id and a name now receive a stable, generated error-message id instead of colliding on a shared -error id and appending a new orphaned error node on every re-validation. (#6055, reported by @webgo-oss)

Updated

  • All dependencies bumped to their latest releases, including four majors β€” Astro 6 β†’ 7 (which pulls in Vite 8), @astrojs/mdx 6 β†’ 7, eslint-plugin-astro 1 β†’ 2, and eslint-plugin-unicorn 68 β†’ 69 β€” plus ESLint, Prettier, Stylelint, PostCSS, Rollup and typescript-eslint. No source changes were required; the full npm run production pipeline (lint + Astro build + bundlewatch) passes and npm audit remains at 0 vulnerabilities. (supersedes Dependabot PRs #6065–#6074)

[4.0.2] - 2026-06-11

Fixed

  • #6048: npm run production no longer fails β€” the dev-only scripts/social-preview.mjs tripped 9 ESLint errors when building from source. The script is now lint-clean. (reported by @lfiorini)

Security

  • Hardened the social preview script's static file server against path traversal and error detail leakage (CodeQL alerts #87–#92). Dev-only script, not part of the npm package.

[4.0.1] - 2026-06-11

Added

  • Official framework integrations announced β€” AdminLTE 4 is now available as first-class packages for four ecosystems, maintained under ColorlibHQ:
    • adminlte-vue β€” Vue 3 & Nuxt, 45+ typed components, composables, SSR-safe theming, ⌘K command palette
    • adminlte-react β€” React & Next.js (App Router / RSC), 30+ typed components, dark mode, ⌘K command palette
    • adminlte-django β€” config-driven sidebar menu, 33+ components, themed django.contrib.admin, {{ form }} renderer
    • adminlte-laravel β€” Blade integration, Vite-ready
  • Social preview generator script (scripts/social-preview.mjs, dev-only β€” excluded from the npm package).

Fixed

  • #6043: Stored color mode is now applied before first paint β€” no more light-mode flash when reloading a page in dark mode. (reported by @bsshreesha)
  • #6044: Long subject/preview text in the mailbox inbox list is truncated instead of overflowing its container. (reported by @Oscurlo)
  • #6038: accessibility.js no longer assigns role="navigation" to ``/<ol> elements, which broke list semantics and failed the Lighthouse accessibility audit. (reported by @lfiorini)

Updated

  • All dependencies bumped to latest; axios pinned via npm overrides to clear a transitive advisory. npm audit remains at 0 vulnerabilities.

[4.0.0] - 2026-05-19

Added

  • 18 new demo pages dramatically expanding the page catalog:
    • Apps: Calendar (FullCalendar 6, drag-and-drop scheduling), Kanban (SortableJS, lane-to-lane drag), Chat (full-page conversation app), File Manager (grid + list views, folder tree), Projects (list with status, progress, team avatars)
    • Mailbox: Inbox, Read Message, Compose β€” three-pane workflow with folders + labels
    • Forms: Wizard (4-step with per-step validation + review summary)
    • Tables: Data Tables β€” jQuery-free implementation using Tabulator 6
    • Pages: Profile (tabbed activity/timeline/settings), Settings (account / notifications / security / billing / danger zone), Invoice (print-ready with totals math), Pricing (3-tier + comparison table + billing toggle), FAQ (data-driven accordions)
    • Error pages: 404, 500, Maintenance
  • Recommended Integrations docs page (docs/integrations.html) β€” copy-paste install snippets for Flatpickr, Tom Select, noUiSlider, Pickr, IMask, Dropzone, FilePond, Quill, EasyMDE, Toast UI Editor, ApexCharts, Chart.js, Tabulator, FullCalendar, SortableJS, GLightbox, and a comparison of icon libraries. AdminLTE doesn't bundle these β€” the page shows how to drop them in.
  • Visible color-mode toggle in the default topbar (#6010) β€” Light / Dark / Auto dropdown with localStorage persistence and prefers-color-scheme integration. Wiring JS shipped globally in _scripts.astro so the toggle works on every demo page automatically.

Changed

  • Forms section restructured: Split the 623-line forms/general.html into three focused pages β€” forms/elements.html (inputs, selects, checks/radios/switches, ranges, floating labels), forms/layout.html (horizontal, inline, sizing, width), and forms/validation.html (Bootstrap native validation + tooltip variant).

Fixed

  • #6028: Color-mode "Auto" icon now renders. The dropdown used bi-circle-fill-half-stroke (a FontAwesome class name); replaced with the correct Bootstrap Icons class bi-circle-half.
  • #6026: .table-head-fixed now respects dark mode. Hardcoded background-color: $white swapped for var(--bs-body-bg) so the sticky header tracks the active colour scheme.
  • #6021: Fullscreen button no longer shifts after click. The plugin used to set iconMaximize.style.display = 'block' directly, overriding the icon library's natural display value (visible on FontAwesome). Now toggles Bootstrap's .d-none utility class. Markup updated accordingly.
  • #6020: Sidebar (including brand) now stays put when using .fixed-header. New companion rule pins the sidebar sticky on sidebar-expand-* breakpoints, giving the menu its own scrollbar instead of scrolling with the page.
  • #6019: Pie chart on index2.html no longer flickers on browser zoom. Pinned an explicit height: 350 to break the ApexCharts ResizeObserver feedback loop most visible on Edge.

Updated

  • Bootstrap variable sync with 5.3.4:
    • Added missing null variables: $btn-close-filter, $carousel-control-icon-filter
    • Marked deprecated-in-5.3.4 variables with inline comments: $btn-close-white-filter, $carousel-dark-indicator-active-bg, $carousel-dark-caption-color, $carousel-dark-control-icon-filter
    • Documented the intentional override of $modal-content-color (null vs Bootstrap's var(--bs-body-color))
  • All dependencies bumped to latest, including majors:
    • eslint: 9.39.4 β†’ 10.4.0
    • typescript: 5.9.3 β†’ 6.0.3 (baseUrl removed, moduleResolution: "bundler" set to clear deprecation warnings)
    • stylelint: 16.26.1 β†’ 17.11.1
    • eslint-plugin-unicorn: 62 β†’ 64
    • astro: 6.0.0 β†’ 6.3.3
    • autoprefixer: 10.4.27 β†’ 10.5.0
    • postcss: 8.5.8 β†’ 8.5.14
    • rollup: 4.59.0 β†’ 4.60.4
    • sass: 1.97.3 β†’ 1.99.0
    • terser: 5.46.0 β†’ 5.47.1
    • bundlewatch: 0.4.1 β†’ 0.4.2, fs-extra: 11.3.4 β†’ 11.3.5, prettier: 3.8.1 β†’ 3.8.3
  • ESLint config flattened: Removed the legacy .eslintrc.json (ignored since ESLint 9), along with three deps that only it referenced: eslint-plugin-import, eslint-config-xo, eslint-config-xo-typescript. Added explicit deps for @eslint/js, globals, and typescript-eslint (now required to be declared since ESLint 10 stopped resolving them transitively).
  • stylelint-config-twbs-bootstrap peer pinned via npm overrides so it accepts stylelint 17 ahead of an upstream release. This removes the need for --legacy-peer-deps on npm install.
  • Security: yaml pinned to ^2.9.0 via overrides to clear the dev-only stack-overflow advisory chain through @astrojs/check. npm audit now reports 0 vulnerabilities.

[4.0.0-rc7] - 2026-03-10

Added

  • Sidebar Without Hover: New sidebar-without-hover body class that prevents collapsed mini sidebar from expanding on hover (#5837)
    • New demo page at layout/collapsed-sidebar-without-hover.html
  • JavaScript Documentation: Added documentation pages for all JS components
    • Layout, Card Widget, Direct Chat, Fullscreen, and Accessibility modules
    • Updated PushMenu documentation with configuration options and responsive behavior
    • All 7 JS components now fully documented in the sidebar navigation
  • Accessibility Module: WCAG 2.1 AA compliance features (skip links, focus management, keyboard navigation, reduced motion, live announcements)
  • Print Layout Fix: Sidebar and main content now both visible when printing (#5996)

Changed

  • Sidebar Persistence: enablePersistence now defaults to false (was true). Opt in via data-enable-persistence="true" on .app-sidebar. BREAKING CHANGE
  • Layout Plugin Refactor: Single Layout instance with persistent hold-transition timer, proper cleanup on resize (#5956)
  • PushMenu Plugin Refactor: Single PushMenu instance with proper responsive logic, config from data attributes, mobile-aware state management (#5954)
  • Login/Register Box: Increased width from 360px to 400px (#5963)

Fixed

  • Fixed Footer with Layout Fixed: Footer now stays pinned at the bottom when using both .fixed-footer and .layout-fixed (#5805)
  • Mobile Sidebar Scroll Chaining: Added overscroll-behavior: contain to prevent page scroll when scrolling sidebar on mobile (#5864)
  • Sidebar Flicker on Load: Resolved by layout refactor fixing duplicate initialization and broken hold-transition timer (#5952)
  • Bootstrap Modal Escape Key: Accessibility module no longer intercepts Escape key when a Bootstrap modal is open (#5993)
  • Pagination Border Radius: Fixed calc() syntax in _bootstrap-variables.scss to match Bootstrap 5.3 format (#5951)
  • slideUp/slideDown at Duration 0: Added early return guard for zero/near-zero animation durations (#5964)
  • TypeScript Comments in Build: Added removeComments: true to tsconfig.json for smaller unminified bundles (#5953)

Updated

  • Dependencies: Updated all packages to latest semver-compatible versions
    • astro: 5.x β†’ 6.0.0
    • @astrojs/mdx: 4.x β†’ 5.0.0
    • @astrojs/check: 0.9.6 β†’ 0.9.7
    • @rollup/plugin-typescript: 12.3.0 β†’ 12.3.1
    • @typescript-eslint/eslint-plugin: 8.48.1 β†’ 8.50.0
    • @typescript-eslint/parser: 8.48.1 β†’ 8.50.0
    • autoprefixer: 10.4.22 β†’ 10.4.23
    • eslint: 9.39.1 β†’ 9.39.4
    • postcss: 8.5.7 β†’ 8.5.8
    • rollup: 4.53.3 β†’ 4.59.0
    • sass: 1.94.2 β†’ 1.97.3
    • terser: 5.44.1 β†’ 5.44.3
    • typescript: 5.9.2 β†’ 5.9.3
  • Bundlewatch: Updated adminlte.js size limit to 5.2 kB

[4.0.0-rc6] - 2025-12-08

Security

  • Fixed 4 Security Vulnerabilities: Resolved all npm audit security issues
    • Fixed high severity reflected XSS vulnerability in Astro server islands
    • Fixed moderate severity authentication bypass via url.pathname in Astro
    • Fixed moderate severity stored XSS in Astro Cloudflare adapter /_image endpoint
    • Fixed moderate severity unsanitized class attribute in mdast-util-to-hast
    • All vulnerabilities resolved by updating to Astro 5.16.4+

Added

  • Sidebar State Persistence: New feature to remember sidebar collapsed/expanded state
    • Sidebar state now persists across page refreshes using localStorage
    • Configurable via enablePersistence option (default: true)
    • SSR-safe implementation with proper environment checks
    • Mobile-aware: doesn't restore state on small screens (respects responsive breakpoints)
    • Graceful error handling for private browsing mode
    • Storage key: lte.sidebar.state

Changed

  • GitHub Actions: Updated all workflows to Node.js 22 (from Node.js 18)
    • Node.js 18 reached End-of-Life on April 30, 2025
    • Node.js 22 is the current Active LTS (supported until April 2027)
    • Updated setup-node action from v3 to v4 across all workflows
    • Updated CodeQL actions from v2 to v3
    • Added FORCE_COLOR: 2 environment variable to codeql.yml for consistency

Fixed

  • Release Workflow: Fixed zip command in release.yml
    • Corrected -d flag to -r for recursive directory zipping
    • Fixed filename inconsistency in release artifacts
  • Nested Card Expand Icon: Fixed issue #5909 where nested collapsed cards didn't show expand icon
    • Updated CSS selectors to use direct child (>) scoping for card state icons
    • Collapse/expand icons now correctly display for nested cards independently
    • Card body/footer display rules now only affect direct children, not nested cards
  • Card Widget JavaScript: Fixed nested card collapse/expand affecting child cards
    • Added :scope > selector to only target direct card-body/footer children
    • Prevents parent card collapse from affecting nested card animations

Updated

  • Dependencies: Updated 15+ packages to latest versions
    • @astrojs/check: 0.9.5 β†’ 0.9.6
    • @astrojs/mdx: 4.3.9 β†’ 4.3.12
    • @rollup/plugin-typescript: 12.1.3 β†’ 12.3.0
    • @typescript-eslint/eslint-plugin: 8.46.2 β†’ 8.48.1
    • @typescript-eslint/parser: 8.46.2 β†’ 8.48.1
    • astro: 5.15.6 β†’ 5.16.4 (includes security fixes)
    • autoprefixer: 10.4.21 β†’ 10.4.22
    • eslint: 9.39.0 β†’ 9.39.1
    • eslint-plugin-astro: 1.4.0 β†’ 1.5.0
    • nodemon: 3.1.10 β†’ 3.1.11
    • prettier: 3.5.3 β†’ 3.7.4
    • rimraf: 6.1.0 β†’ 6.1.2
    • rollup: 4.52.4 β†’ 4.53.3
    • sass: 1.93.2 β†’ 1.94.2
    • stylelint: 16.25.0 β†’ 16.26.1
    • terser: 5.44.0 β†’ 5.44.1

[4.0.0-rc5] - 2025-10-14

Updated

  • Dependencies: Updated 17+ packages to latest versions for improved security and performance
    • @astrojs/mdx: 4.3.0 β†’ 4.3.7
    • @typescript-eslint/eslint-plugin: 8.36.0 β†’ 8.46.1
    • @typescript-eslint/parser: 8.36.0 β†’ 8.46.1
    • astro: 5.11.0 β†’ 5.14.4
    • bootstrap: 5.3.7 β†’ 5.3.8
    • concurrently: 9.2.0 β†’ 9.2.1
    • cross-env: 7.0.3 β†’ 10.1.0 (major version)
    • eslint: 9.30.1 β†’ 9.37.0
    • eslint-config-xo: 0.47.0 β†’ 0.49.0
    • eslint-config-xo-typescript: 8.0.1 β†’ 9.0.0 (major version)
    • eslint-plugin-unicorn: 59.0.1 β†’ 61.0.2
    • fs-extra: 11.3.0 β†’ 11.3.2
    • rollup: 4.44.2 β†’ 4.52.4
    • sass: 1.89.2 β†’ 1.93.2
    • stylelint: 16.21.1 β†’ 16.25.0
    • terser: 5.43.1 β†’ 5.44.0
    • typescript: 5.8.3 β†’ 5.9.3

Fixed

  • Security Vulnerabilities: Resolved 2 security issues
    • Fixed high severity DoS vulnerability in axios (updated to 0.30.2+)
    • Fixed critical vulnerability in form-data random function (updated to 4.0.4+)

Removed

  • Deprecated Files: Removed .eslintignore file
    • ESLint ignores are now properly configured in eslint.config.js
    • Eliminates deprecation warnings in ESLint 9.x

[4.0.0-rc4] - 2025-07-10

Updated

  • Dependencies: Updated 8 packages to latest versions
    • @rollup/plugin-typescript: 12.1.3 β†’ 12.1.4
    • @typescript-eslint/eslint-plugin: 8.35.1 β†’ 8.36.0
    • @typescript-eslint/parser: 8.35.1 β†’ 8.36.0
    • astro: 5.10.0 β†’ 5.11.0
    • eslint: 9.30.0 β†’ 9.30.1
    • prettier: 3.5.3 β†’ 3.6.2
    • rollup: 4.44.0 β†’ 4.44.2
    • stylelint: 16.21.0 β†’ 16.21.1

Fixed

  • Windows Build Compatibility: Fixed npm scripts to work cross-platform by replacing Unix-specific shell commands with shx
    • Updated copy-assets script to use shx mkdir and shx cp commands
    • Updated flatten-build script to use shx cp and shx rm commands
    • Added shx package as dev dependency for cross-platform shell command support
    • Resolves build failures on Windows systems with "The syntax of the command is incorrect" errors
  • TeamViewer Modal Compatibility: Fixed modal fade animations for remote desktop compatibility
    • Updated accessibility CSS to use transition: none and opacity: 1 instead of display: block
    • Maintains WCAG 2.1 AA compliance while ensuring modals work properly in TeamViewer sessions
    • Added specific transform overrides for modal dialogs in reduced motion mode
  • Mobile Sidebar Scrolling: Fixed sidebar closing unexpectedly when scrolling on mobile devices
    • Updated touch event handling to differentiate between tap and scroll gestures
    • Added proper overflow properties to sidebar wrapper for mobile viewport
    • Sidebar now remains open during scroll operations on touch devices
    • Resolves issue where scrolling in sidebar would immediately close it on mobile browsers
  • Image Path Resolution: Fixed mobile image loading by using relative paths in HTML
    • Root Cause: Absolute paths like /assets/img/user.jpg caused 404 errors on mobile
    • Solution: Generate relative image paths in Astro components based on page location
    • Result: Images now load correctly on all devices and deployment scenarios

[4.0.0-rc3] - 2025-06-24

Production Deployment & Cross-Platform Compatibility

This release resolves critical production deployment issues and ensures consistent behavior between development and production environments across different deployment scenarios.

πŸš€ **Production Deployment Fixes**

**Path Resolution System**

  • Smart Path Resolution: Implemented intelligent relative path calculation for all assets
    • CSS/JS paths automatically adjust based on page depth (e.g., ./css/ for root, ../css/ for sub-pages)
    • Image paths dynamically corrected at runtime for any deployment structure
    • Works seamlessly for root deployment, sub-folder deployment, and CDN hosting

**RTL CSS Processing Fix**

  • PostCSS Configuration: Fixed rtlcss plugin interference with LTR builds
    • rtlcss now only runs during RTL-specific builds (NODE_ENV=RTL)
    • Prevents automatic left/right property flipping in standard production builds
    • Maintains separate .rtl.css files for right-to-left language support

**Image Loading Resolution**

  • Runtime Image Path Fix: Added intelligent image path correction script
    • Detects deployment context from working CSS/JS paths
    • Automatically converts absolute image paths (/assets/img/...) to relative paths
    • Ensures images load correctly regardless of deployment sub-folder structure

🎨 **UI/Navigation Improvements**

  • Badge & Arrow Positioning: Resolved sidebar layout issues
    • Fixed nav badges overlapping text elements
    • Restored chevron arrow indicators for expandable menu items
    • Corrected spacing and visual hierarchy in sidebar navigation
    • Added sidebar-open class to all layouts for consistent styling

**Cross-Device Consistency**

  • Full-Width Navigation Links: Enhanced clickable areas
    • Set .sidebar-menu .nav-link { width: 100%; } for better UX
    • Ensures badges and arrows align properly at the far right edge
    • Maintains proper spacing across all screen sizes and devices

πŸ“¦ **CDN & Dependencies**

**Updated to Latest Stable Versions**

  • Bootstrap: v5.3.3 β†’ v5.3.7 (latest stable)
  • Bootstrap Icons: v1.11.3 β†’ v1.13.1 (latest with new icons)
  • OverlayScrollbars: v2.10.1 β†’ v2.11.0 (performance improvements)
  • PopperJS: v2.11.8 (confirmed latest - no change needed)

**Integrity Attribute Removal**

  • SRI-Free CDN Loading: Removed integrity attributes from all CDN resources
    • Prevents "Failed to find a valid digest" console errors
    • Allows CDN providers to update files without breaking existing links
    • Maintains crossorigin="anonymous" for security while removing brittle SRI checks

πŸ› οΈ **Build System Enhancements**

**Development vs Production Parity**

  • Unified Asset Pipeline: Both dev and production now use identical asset resolution
    • Development copies fresh CSS/JS to src/html/public/ for hot-reloading
    • Production builds CSS/JS to dist/css/ and dist/js/ then flattens structure
    • Smart path resolution ensures consistent behavior in both environments

**Git Repository Cleanup**

  • Production Build Distribution: Added complete dist/ folder to repository
    • Provides ready-to-use production files for immediate deployment
    • Simplifies distribution and CDN access via jsDelivr
    • Enables direct download without requiring Node.js build environment

πŸ› **Critical Bug Fixes**

**Console Errors Eliminated**

  • SortableJS Loading: Fixed CDN integrity mismatch for SortableJS
  • Asset Path Errors: Resolved 404 errors for images in sub-folder deployments
  • ESLint Compliance: Fixed prefer-global-this and prefer-string-slice linting issues

**Cross-Browser Compatibility**

  • Modern Browser Support: Updated all CDN references to use stable, versioned URLs
  • Legacy Browser Fallbacks: Maintained compatibility while leveraging modern features
  • Touch Device Optimization: Enhanced touch target sizing and navigation

πŸ“Š **Performance & Reliability**

**Bundle Analysis**

  • Size Optimization: All bundle watch checks pass with updated thresholds
  • Loading Performance: Faster initial page load with optimized asset delivery
  • Runtime Performance: Minimal overhead from path resolution scripts (<1ms execution)

**Deployment Versatility**

  • FTP Deployment: Full support for traditional FTP/SFTP deployment workflows
  • Static Hosting: Compatible with GitHub Pages, Netlify, Vercel, Cloudflare Pages
  • Sub-folder Deployment: Works seamlessly when deployed to /themes/v4/ or similar paths
  • CDN Integration: Ready for integration with content delivery networks

🎯 **Quality Assurance**

**Testing Coverage**

  • Development Environment: npm run dev - all features verified working
  • Production Build: npm run production - 37 pages built successfully, 0 errors
  • Static Serving: python3 -m http.server - full functionality confirmed
  • Sub-folder Deployment: Tested with various deployment paths and structures

**Linting & Standards**

  • Zero Linting Errors: Complete compliance with ESLint and StyleLint rules
  • Code Consistency: Unified code style across all JavaScript and CSS files
  • Best Practices: Modern ES2022+ patterns with proper browser compatibility

πŸš€ **Deployment Guide**

**Quick Start**

bash
# Build for production
npm run production

# Deploy via FTP (upload entire dist/ folder contents)
# Or serve locally for testing
cd dist && python3 -m http.server 8080

**Deployment Scenarios**

  1. Root Deployment: Upload dist/ contents to public_html/ or equivalent
  2. Sub-folder Deployment: Upload dist/ contents to public_html/admin/ or similar
  3. Static Host Deployment: Point build directory to dist/ in your hosting platform
  4. CDN Integration: Upload assets to CDN and update paths as needed

πŸ“‹ **Migration Notes**

**From 4.0.0-rc2 to 4.0.0-rc3**

Automatic Updates (No Action Required):

  • Path resolution works automatically in all deployment scenarios
  • Image loading is fixed without any HTML changes needed
  • Sidebar navigation displays correctly with proper spacing and indicators
  • All CDN resources load without console errors

Recommended Actions:

  • Remove any manual path fixes you may have implemented
  • Update your deployment process to use the new dist/ structure
  • Verify image loading in your specific deployment environment
  • Test both development (npm run dev) and production builds

Breaking Changes:

  • None - this release is fully backward compatible with existing HTML and CSS

[4.0.0-rc2] - 2025-06-20

ES2022 Modernization & Accessibility Compliance

This release modernizes AdminLTE to ES2022 standards and implements comprehensive WCAG 2.1 AA accessibility compliance, making it one of the most accessible admin templates available.

JavaScript & Build System

πŸš€ **ES2022 Upgrade**

  • TypeScript Target: Upgraded from ES6 to ES2022
    • Enables modern JavaScript features: optional chaining, nullish coalescing, class fields
    • Improved performance with native modern browser optimizations
    • Better tree-shaking and smaller bundle sizes
  • Browser Support: Updated .browserslistrc for ES2022 compatibility
    • Chrome β‰₯97, Firefox β‰₯104, Safari β‰₯15.4, Edge β‰₯97
    • Removed Internet Explorer support (end-of-life)
  • Build Configuration: Enhanced Rollup configuration
    • Native ES2022 module output
    • Improved source map generation
    • TypeScript integration optimization

β™Ώ **Accessibility Features**

**WCAG 2.1 AA Compliance Implementation**

  • New Accessibility Module: Complete AccessibilityManager class (src/ts/accessibility.ts)
    • Automatic skip links generation and management
    • ARIA live regions for dynamic content announcements
    • Enhanced focus management and keyboard navigation
    • Screen reader compatibility (JAWS, NVDA, VoiceOver)
    • Form validation with accessible error handling

**Core Accessibility Features:**

  • Skip Navigation: Automatic skip links to main content, navigation, and key sections
  • Focus Management:
    • Enhanced focus indicators meeting contrast requirements
    • Focus trapping for modals and dropdowns
    • Logical tab order management
  • Keyboard Navigation:
    • Full keyboard accessibility for all interactive elements
    • Arrow key navigation for menus and tree views
    • Escape key handling for closing modals/dropdowns
  • Screen Reader Support:
    • Proper ARIA labels, roles, and properties
    • Live region announcements for dynamic content
    • Semantic HTML structure with landmarks
  • Form Accessibility:
    • Automatic error identification and announcement
    • Required field indicators
    • Proper label associations

**Responsive Design & Preferences:**

  • Reduced Motion: Respects prefers-reduced-motion for users with vestibular disorders
  • High Contrast: Enhanced support for high contrast mode
  • Touch Targets: Minimum 44Γ—44 pixel touch targets (WCAG 2.5.8)
  • Color Contrast: All color combinations meet 4.5:1 contrast ratio requirement

🎨 **Accessibility Styles**

**New Stylesheet:** `src/scss/_accessibility.scss`

  • Skip Link Styling: Visually hidden until focused, proper positioning
  • Enhanced Focus Indicators: 3px outline with high contrast colors
  • Screen Reader Utilities: .sr-only and .sr-only-focusable classes
  • Touch Target Sizing: Utilities for ensuring minimum touch target sizes
  • Accessible Color Palette: Pre-defined colors meeting contrast requirements
  • Print Accessibility: Enhanced print styles with visible URLs and borders

πŸ—οΈ **Component Improvements**

**Enhanced Components:**

  • Header Component: Improved meta tags with accessibility features
    • Color scheme support (light/dark)
    • Theme color meta tags for browser UI
    • Enhanced viewport configuration
    • Accessibility description updates
  • Navigation Components:
    • Proper ARIA roles and labels
    • Semantic navigation landmarks
    • Keyboard navigation support
    • Screen reader announcements

πŸ“š **Documentation & Compliance**

**Added:**

  • ACCESSIBILITY-COMPLIANCE.md: Comprehensive documentation
    • Implementation guide for developers
    • Testing procedures and tools
    • Browser and assistive technology compatibility
    • API documentation for accessibility features
    • Usage examples and best practices

**API Reference:**

javascript
// Initialize accessibility features
const a11y = initAccessibility({
  announcements: true,      // Enable live announcements
  skipLinks: true,         // Add skip navigation links
  focusManagement: true,   // Enhanced focus handling
  keyboardNavigation: true, // Full keyboard support
  reducedMotion: true      // Respect motion preferences
});

// Public methods
a11y.announce('Content updated', 'polite');
a11y.focusElement('#main-content');
a11y.trapFocus(modalElement);
a11y.addLandmarks();

πŸ”§ **Technical Improvements**

**Build System:**

  • Zero Linting Errors: All CSS and JavaScript pass strict linting rules
  • Bundle Impact: Minimal size increase (~23KB total for accessibility features)
  • Performance: <5ms initialization time for accessibility features
  • Integration: Seamless integration with existing AdminLTE architecture

**Browser Compatibility:**

  • Modern Browsers: Full ES2022 support in target browsers
  • Assistive Technology: Tested with leading screen readers
  • Mobile Support: Enhanced touch and mobile accessibility
  • Legacy Graceful Degradation: Core functionality maintained for older browsers

πŸ› **Bug Fixes**

**Layout Issues:**

  • Sidebar Component: Fixed corrupted navigation structure
  • Header Navigation: Resolved parsing errors in Astro components
  • CSS Compilation: Fixed SASS deprecation warnings and property order issues
  • Focus Indicators: Corrected outline and focus ring implementations

**Linting Compliance:**

  • CSS: Fixed 72+ StyleLint violations in accessibility styles
  • JavaScript: Resolved ESLint violations for numeric separators and function scoping
  • TypeScript: Fixed compilation errors with modern syntax

πŸ“Š **Performance Metrics**

**Bundle Sizes:**

  • CSS: ~357KB (includes full accessibility features)
  • JavaScript: ~47KB (includes AccessibilityManager)
  • Gzipped Impact: <10KB additional for complete accessibility suite

**Lighthouse Scores:**

  • Accessibility: 100% (WCAG 2.1 AA compliant)
  • Performance: Maintained existing performance levels
  • Best Practices: Improved with modern JavaScript patterns

🎯 **Standards Compliance**

**WCAG 2.1 AA Requirements Met:**

  • 1. Perceivable: Text alternatives, semantic structure, color contrast
  • 2. Operable: Keyboard accessibility, no seizure triggers, sufficient time
  • 3. Understandable: Readable content, predictable functionality, input assistance
  • 4. Robust: Valid markup, assistive technology compatibility

**Additional Standards:**

  • Section 508: US federal accessibility requirements
  • EN 301 549: European accessibility standard
  • ADA Compliance: Americans with Disabilities Act requirements

πŸš€ **Migration Guide**

**From 4.0.0-rc1 to 4.0.0-rc2:**

Automatic Features (No Action Required):

  • Accessibility features initialize automatically
  • Skip links appear automatically for keyboard users
  • Focus management works out-of-the-box
  • Screen reader announcements are enabled by default

Optional Enhancements:

html
<!-- Add accessibility-enhanced form -->
<form class="needs-validation" novalidate>
  <div class="mb-3">
    <label for="email" class="form-label">Email <span class="required-indicator">*</span></label>
    <input type="email" class="form-control" id="email" required>
    <div class="invalid-feedback" role="alert"></div>
  </div>
</form>

<!-- Use accessible color classes -->
<div class="alert alert-success text-accessible-success">Success message</div>

For Developers:

  • Include accessibility utilities: import { accessibilityUtils } from './adminlte.js'
  • Use new CSS classes: .sr-only, .touch-target, .text-accessible-*
  • Test with screen readers and keyboard navigation

[4.0.0-rc1] - 2025-06-20

Major Modernization Release

This release represents a complete modernization of the AdminLTE codebase, bringing it up to current standards with the latest tooling, dependencies, and best practices.

Infrastructure & Tooling

Added

  • New npm scripts for better developer experience:
    • npm start - Quick development server launch
    • npm run build - Convenient build command for development
    • Enhanced npm run production with bundlewatch integration

Changed

  • Upgraded to ES Modules: Added "type": "module" to package.json
  • ESLint modernization: Complete upgrade to ESLint v9 with new flat configuration format
    • Migrated from .eslintrc.json to modern eslint.config.js
    • Updated all ESLint plugins to latest versions
    • Resolved 700+ linting issues through automated fixes
  • Astro configuration: Added output: 'static' for proper static site generation
  • Build optimizations: Enhanced Rollup and PostCSS configurations for ES modules

Dependencies

Updated

  • Major version upgrades:

    • astro: 4.15.12 β†’ 5.10.0
    • eslint: 8.57.1 β†’ 9.29.0
    • @typescript-eslint/eslint-plugin: 7.18.0 β†’ 8.34.1
    • @typescript-eslint/parser: 7.18.0 β†’ 8.34.1
    • eslint-config-xo: 0.44.0 β†’ 0.47.0
    • eslint-config-xo-typescript: 4.0.0 β†’ 8.0.1
    • eslint-plugin-astro: 0.34.0 β†’ 1.3.1
    • eslint-plugin-unicorn: 52.0.0 β†’ 59.0.1
    • stylelint-config-twbs-bootstrap: 15.1.0 β†’ 16.1.0
  • Minor/patch updates (50+ packages):

    • bootstrap: 5.3.3 β†’ 5.3.7
    • sass: 1.78.0 β†’ 1.89.2
    • typescript: 5.6.2 β†’ 5.8.3
    • prettier: 3.3.3 β†’ 3.5.3
    • And many more...

Security & Quality

Fixed

  • Resolved all npm security vulnerabilities (0 vulnerabilities remaining)
  • Updated browserslist database to latest browser compatibility data
  • Fixed all deprecation warnings from SASS and other build tools

Improved

  • Code quality: Zero linting errors across JavaScript, TypeScript, CSS, and Astro files
  • Bundle optimization: All assets pass bundlewatch size thresholds
  • Build reliability: Complete production build pipeline works end-to-end

Code Cleanup

Removed

  • Technical debt cleanup:
    • Removed incomplete TODO comments and dead code
    • Cleaned up unused variables and imports
    • Standardized code formatting across all files

Fixed

  • ESLint configuration conflicts between different rule sets
  • Module import issues with JSON imports in ES modules
  • Circular dependency warnings in build tools

Development Experience

Enhanced

  • Faster development startup with optimized watch tasks
  • Cleaner build output with suppressed non-critical warnings
  • Better error reporting with modern linting tools
  • Improved script organization with consistent naming conventions

Browser & Platform Support

Maintained

  • Full Bootstrap 5.3.7 compatibility
  • Modern browser support with updated browserslist
  • Static site generation with Astro 5.x
  • RTL (Right-to-Left) language support

Previous Versions

[4.0.0-beta3] and earlier

  • Legacy versions with previous dependency sets
  • See git history for detailed changes in earlier versions

Migration Guide

From 4.0.0-beta3 to 4.0.0-rc1

For Users:

  • No breaking changes in the compiled CSS/JS output
  • All existing HTML templates remain fully compatible
  • CDN links and package imports work as before

For Developers:

  • Update your npm scripts if you were using custom ones
  • The new npm start command replaces manual npm run dev
  • ESLint configuration is now in eslint.config.js (old .eslintrc.json removed)
  • Build process now requires Node.js ES modules support

Recommended Actions:

  1. Run npm install to get latest dependencies
  2. Use npm start for development
  3. Use npm run production for production builds
  4. Review any custom ESLint configurations for compatibility

File: CODE_OF_CONDUCT.md

Contributor Covenant Code of Conduct

Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

Our Standards

Examples of behavior that contributes to creating a positive environment include:

  • Using welcoming and inclusive language
  • Being respectful of differing viewpoints and experiences
  • Gracefully accepting constructive criticism
  • Focusing on what is best for the community
  • Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

  • The use of sexualized language or imagery and unwelcome sexual attention or advances
  • Trolling, insulting/derogatory comments, and personal or political attacks
  • Public or private harassment
  • Publishing others' private information, such as a physical or electronic address, without explicit permission
  • Other conduct which could reasonably be considered inappropriate in a professional setting

Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at https://colorlib.com/wp/contact-us/. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct/


File: composer.json

{
"name": "almasaeed2010/adminlte",
"description": "AdminLTE - admin control panel and dashboard that's based on Bootstrap 4",
"homepage": "https://adminlte.io/",
"keywords": [
"css",
"js",
"less",
"responsive",
"back-end",
"template",
"theme",
"web",
"admin"
],
"authors": [
{
"name": "Colorlib"
}
],
"license": "MIT",
"support": {
"issues": "https://github.com/ColorlibHQ/AdminLTE/issues"
}
}


File: package.json

{
"name": "admin-lte",
"description": "Responsive open source admin dashboard and control panel.",
"version": "4.10.1",
"type": "module",
"license": "MIT",
"author": "Colorlib https://colorlib.com",
"main": "dist/js/adminlte.min.js",
"module": "dist/js/adminlte.esm.js",
"types": "dist/js/types/adminlte.d.ts",
"exports": {
".": {
"types": "./dist/js/types/adminlte.d.ts",
"sass": "./src/scss/adminlte.scss",
"style": "./dist/css/adminlte.css",
"import": "./dist/js/adminlte.esm.js",
"require": "./dist/js/adminlte.min.js"
},
"./dist/": "./dist/",
"./src/scss/": "./src/scss/",
"./package.json": "./package.json"
},
"files": [
"dist/css/",
"dist/js/",
"dist/assets/",
"src/scss/",
"src/ts/",
"CHANGELOG.md",
"ACCESSIBILITY-COMPLIANCE.md"
],
"engines": {
"node": ">=20"
},
"devEngines": {
"runtime": {
"name": "node",
"version": "^22.22.3 || ^24.16.0 || >=26.3.0",
"onFail": "error"
},
"packageManager": {
"name": "npm",
"version": ">=10",
"onFail": "error"
}
},
"scripts": {
"start": "npm run dev",
"dev": "npm-run-all --parallel watch docs-serve",
"build": "npm-run-all clean compile",
"css": "npm-run-all css-compile css-prefix css-rtl css-minify",
"css-compile": "sass --style expanded --load-path="node_modules" --source-map --embed-sources --no-error-css --quiet-deps --silence-deprecation=import src/scss/:dist/css/",
"css-rtl": "cross-env NODE_ENV=RTL postcss --config src/config/postcss.config.mjs --dir "dist/css" --ext ".rtl.css" "dist/css/.css" "!dist/css/.min.css" "!dist/css/.rtl.css"",
"css-lint": "stylelint "src/scss/**/
.scss" --cache --cache-location .cache/.stylelintcache --rd",
"css-minify": "npm-run-all --aggregate-output --parallel css-minify-",
"css-minify-main": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix ".min" "dist/css/
.css" "!dist/css/.min.css" "!dist/css/rtl.css"",
"css-minify-rtl": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix ".min" "dist/css/rtl.css" "!dist/css/.min.css"",
"css-prefix": "postcss --config src/config/postcss.config.mjs --replace "dist/css/
.css" "!dist/css/.rtl.css" "!dist/css/.min.css"",
"js": "npm-run-all js-compile js-types js-minify",
"js-compile": "rollup --config src/config/rollup.config.js --sourcemap",
"js-types": "tsc -p tsconfig.types.json",
"js-lint": "eslint --format=stylish .",
"js-minify": "npm-run-all --aggregate-output --parallel js-minify-
",
"js-minify-main": "terser --compress passes=2 --mangle --comments "/^!/" --source-map "content=dist/js/adminlte.js.map,includeSources,url=adminlte.min.js.map" --output dist/js/adminlte.min.js dist/js/adminlte.js",
"js-minify-esm": "terser --compress passes=2 --module --mangle --comments "/^!/" --source-map "content=dist/js/adminlte.esm.js.map,includeSources,url=adminlte.esm.min.js.map" --output dist/js/adminlte.esm.min.js dist/js/adminlte.esm.js",
"bundlewatch": "bundlewatch --config .bundlewatch.config.json",
"lockfile-lint": "lockfile-lint --allowed-hosts npm --allowed-schemes https: --empty-hostname false --type npm --path package-lock.json",
"docs-compile": "npm run copy-assets && astro --config src/config/astro.config.mjs build",
"docs-lint": "astro --config src/config/astro.config.mjs check --tsconfig src/html/tsconfig.json",
"docs-format": "prettier --write "dist/**/.html"",
"docs-serve": "npm run copy-assets && astro --config src/config/astro.config.mjs dev --open --port 3000",
"copy-assets": "rimraf src/html/public && shx mkdir -p src/html/public && shx cp -r dist/css dist/js dist/assets src/html/public/",
"flatten-build": "shx cp -r dist/html/
dist/ && shx rm -rf dist/html && shx rm -rf dist/.astro && shx mkdir -p dist/docs && shx mv dist/docs.html dist/docs/index.html",
"assets": "node src/config/assets.config.mjs",
"version": "node src/config/sync-version.mjs",
"test": "vitest run",
"test-watch": "vitest",
"test-a11y": "node tests/a11y.mjs",
"palette": "node scripts/palette.mjs",
"lint": "npm-run-all --aggregate-output --continue-on-error --parallel js-lint css-lint docs-lint lockfile-lint",
"clean": "rimraf dist",
"compile": "npm-run-all css js assets copy-assets docs-compile docs-format flatten-build",
"production": "npm-run-all clean lint test compile test-size",
"watch": "concurrently "npm:watch-"",
"watch-css-main": "nodemon --watch src/scss/ --ext scss --exec "npm-run-all css-lint css-compile css-prefix copy-assets"",
"watch-css-dist": "nodemon --watch dist/css/ --ext css --ignore "dist/css/
.rtl.*" --exec "npm run css-rtl"",
"watch-js-main": "nodemon --watch src/ts/ --ext ts --exec "npm-run-all js-lint js-compile copy-assets"",
"watch-assets": "nodemon --watch src/assets/ --exec "npm run assets"",
"test-browser": "node tests/browser.mjs",
"test-size": "node scripts/check-size.mjs"
},
"keywords": [
"css",
"sass",
"responsive",
"admin",
"template",
"theme",
"framework",
"control-panel",
"dashboard"
],
"homepage": "https://adminlte.io",
"style": "dist/css/adminlte.css",
"sass": "src/scss/adminlte.scss",
"repository": {
"type": "git",
"url": "git://github.com/ColorlibHQ/AdminLTE.git"
},
"bugs": {
"url": "https://github.com/ColorlibHQ/AdminLTE/issues"
},
"peerDependencies": {
"bootstrap": "^5.3.8"
},
"overrides": {
"axios": "^1.20.0",
"shell-quote": "^1.12.0",
"stylelint-config-twbs-bootstrap": {
"stylelint": "$stylelint"
},
"yaml": "^2.9.1",
"postcss-selector-parser": "^7.1.6"
},
"devDependencies": {
"@astrojs/check": "^0.9.10",
"@astrojs/markdown-remark": "^7.3.2",
"@astrojs/mdx": "^8.0.3",
"@eslint/js": "^10.0.1",
"@rollup/plugin-typescript": "^12.3.0",
"@typescript-eslint/eslint-plugin": "^8.71.1",
"@typescript-eslint/parser": "^8.71.1",
"astro": "^7.3.7",
"autoprefixer": "^10.6.1",
"axe-core": "^4.14.0",
"bootstrap": "^5.3.8",
"bundlewatch": "^0.4.2",
"clean-css-cli": "^5.6.3",
"concurrently": "^10.0.5",
"cross-env": "^10.1.0",
"eslint": "^10.12.0",
"eslint-plugin-astro": "^3.2.1",
"eslint-plugin-unicorn": "^77.0.0",
"fs-extra": "^11.4.1",
"globals": "^17.13.0",
"happy-dom": "^20.14.5",
"lockfile-lint": "^5.0.1",
"nodemon": "^3.1.14",
"npm-run-all2": "^9.0.3",
"playwright": "^1.64.0",
"postcss": "^8.5.29",
"postcss-cli": "^12.0.0",
"prettier": "^3.9.9",
"prettier-plugin-astro": "^1.1.0",
"rimraf": "^6.1.3",
"rollup": "^4.64.2",
"rtlcss": "^4.3.0",
"sass": "^1.105.1",
"shx": "^0.4.0",
"stylelint": "^17.16.0",
"stylelint-config-twbs-bootstrap": "^16.1.0",
"terser": "^5.51.2",
"tslib": "^2.8.1",
"typescript": "^6.0.3",
"typescript-eslint": "^8.71.1",
"unist-util-visit": "^5.1.0",
"vitest": "^5.0.3"
}
}


File: SECURITY.md

Security Policy

Reporting Security Issues

If you discover a security vulnerability in AdminLTE, please report it by emailing the maintainers or opening a private security advisory on GitHub. We take security seriously and will respond promptly.

Supported Versions

Version Supported
4.x :white_check_mark:
3.x :x:
< 3.0 :x:

Security Best Practices

AdminLTE is a frontend admin dashboard template. When deploying applications built with AdminLTE, follow these security best practices:

1. Production Deployment

  • Never expose node_modules directory in production environments
  • Remove demo/example files (like index2.html, index3.html) from production builds
  • Use a proper build process that only includes necessary production assets
  • Configure web server properly to prevent directory traversal attacks

2. Build Process

When deploying to production:

bash
# Build only production assets
npm run production

# Deploy only the dist/ directory contents you need
# Typically: dist/js/adminlte.min.js and dist/css/adminlte.min.css

3. What NOT to Deploy

Do not deploy these to production:

  • node_modules/ directory
  • Example/demo HTML files (index.html, index2.html, index3.html, etc.)
  • Source files (src/ directory)
  • Development configuration files
  • Documentation files

Known CVE Issues

CVE-2021-36471 (Disputed)

Status: This CVE is disputed and does not represent a vulnerability in AdminLTE itself.

Issue: CVE-2021-36471 claims AdminLTE 3.1.0 has a "Directory Traversal vulnerability" that allows remote attackers to view demo pages via /admin/index2.html and /admin/index3.html.

Clarification:

  • The index2.html and index3.html files are example/demo pages intended for developer reference during development
  • This is not a vulnerability in AdminLTE - it is a deployment misconfiguration by website developers
  • The issue occurs when developers incorrectly deploy:
    • Their entire node_modules folder publicly
    • Demo/example files in production environments
    • Without proper web server configuration

Resolution:

  • AdminLTE 4.x has restructured the project architecture with clear separation between development demos and production assets
  • Follow the production deployment best practices above
  • Only deploy the compiled production assets from dist/js/ and dist/css/
  • The original CVE researcher acknowledged this should be classified as low/info severity, not critical

For More Information:

Secure Development

Content Security Policy (CSP)

When implementing AdminLTE in your application, consider adding appropriate Content Security Policy headers to prevent XSS attacks.

Authentication & Authorization

AdminLTE is a UI template only and does not include authentication or authorization. You must:

  • Implement proper authentication in your backend
  • Secure all API endpoints
  • Use HTTPS in production
  • Implement proper session management
  • Follow OWASP security guidelines

Dependencies

  • Keep AdminLTE and its dependencies up to date
  • Regularly run npm audit to check for vulnerabilities
  • Review security advisories for Bootstrap and other dependencies

Contact

For security concerns, please contact the maintainers through:

  • GitHub Issues (for general questions)
  • GitHub Security Advisories (for sensitive security issues)
  • Project maintainer email (check package.json)

File: tsconfig.json

{
"compilerOptions": {
"removeComments": true,
"noFallthroughCasesInSwitch": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noImplicitAny": true,
"module": "esnext",
"moduleResolution": "bundler",
"alwaysStrict": true,
"strict": true,
"strictNullChecks": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"paths": {
"@components/": ["./src/html/components/"],
},
/* Language and Environment /
"target": "es2022",
"lib": ["es2022", "DOM"],
},
"include": [
"src/ts/**/
"
],
"exclude": [
"dist",
"node_modules",
]
}


File: tsconfig.types.json

{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"removeComments": false,
"rootDir": "src/ts",
"outDir": "dist/js/types"
}
}


File: .bundlewatch.config.json

{
"files": [
{
"path": "./dist/css/adminlte.css",
"maxSize": "48 kB"
},
{
"path": "./dist/css/adminlte.min.css",
"maxSize": "43.5 kB"
},
{
"path": "./dist/css/adminlte.rtl.css",
"maxSize": "48 kB"
},
{
"path": "./dist/css/adminlte.rtl.min.css",
"maxSize": "43.5 kB"
},
{
"path": "./dist/css/adminlte-docs.min.css",
"maxSize": "4.5 kB"
},
{
"path": "./dist/css/adminlte-select2.min.css",
"maxSize": "3 kB"
},
{
"path": "./dist/css/adminlte-select2.rtl.min.css",
"maxSize": "3 kB"
},
{
"path": "./dist/css/adminlte-colors.min.css",
"maxSize": "8.5 kB"
},
{
"path": "./dist/css/adminlte-colors.rtl.min.css",
"maxSize": "8.5 kB"
},
{
"path": "./dist/css/adminlte-colors-v3.min.css",
"maxSize": "11 kB"
},
{
"path": "./dist/css/adminlte-colors-v3.rtl.min.css",
"maxSize": "11 kB"
},
{
"path": "./dist/js/adminlte.js",
"maxSize": "12 kB"
},
{
"path": "./dist/js/adminlte.min.js",
"maxSize": "8.5 kB"
},
{
"path": "./dist/js/adminlte.esm.js",
"maxSize": "12 kB"
},
{
"path": "./dist/js/adminlte.esm.min.js",
"maxSize": "8.5 kB"
}
]
}


File: .gitpod.yml

tasks:

  • init: npm install
    command: npm run dev
    ports:
  • port: 3000
    onOpen: open-preview

File: .lgtm.yml

https://lgtm.com/help/lgtm/customizing-file-classification

path_classifiers:
plugins:
- plugins/

extraction:
javascript:
# https://lgtm.com/help/lgtm/javascript-extraction#customizing-index
# The index step extracts information from the files in the codebase.
index:
# Specify a list of files and folders to exclude from extraction.
exclude:
- bower_components/
- docs/assets/js/plugins/
- plugins/


File: .stylelintrc.json

{
"extends": [
"stylelint-config-twbs-bootstrap"
],
"reportInvalidScopeDisables": true,
"reportNeedlessDisables": true,
"overrides": [
{
"files": ["**/.scss"],
"rules": {
"declaration-no-important": null,
"declaration-property-value-disallowed-list": {
"border": "none",
"outline": "none"
},
"function-disallowed-list": [
"calc",
"lighten",
"darken"
],
"keyframes-name-pattern": null,
"property-disallowed-list": [
"border-radius",
"border-top-left-radius",
"border-top-right-radius",
"border-bottom-right-radius",
"border-bottom-left-radius",
"transition"
],
"scss/dollar-variable-default": [
true,
{
"ignore": "local"
}
],
"scss/selector-no-union-class-name": true,
"selector-max-class": null,
"selector-max-combinators": null,
"selector-max-compound-selectors": null,
"selector-max-id": null,
"selector-max-specificity": null,
"selector-max-type": null,
"selector-no-qualifying-type": null
}
},
{
"comment": "Third-party compatibility themes target class names we don't own (Select2 uses BEM, not kebab-case).",
"files": ["src/scss/compat/
.scss"],
"rules": {
"selector-class-pattern": null
}
}
]
}


File: src/html/tsconfig.json

{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"paths": {
"@components/": ["./components/"]
}
},
"include": ["/*.astro", "/*.ts", "../../.astro/types.d.ts"],
"exclude": ["public"]
}


File: src/html/.eslintrc.json

{
"root": true,
"extends": [
"plugin:astro/recommended"
],
"overrides": [
{
// Define the configuration for .astro file.
"files": ["*.astro"],
// Allows Astro components to be parsed.
"parser": "astro-eslint-parser",
// Parse the script in .astro as TypeScript by adding the following configuration.
// It's the setting you need when using TypeScript.
"parserOptions": {
"parser": "@typescript-eslint/parser",
"extraFileExtensions": [".astro"]
},
"rules": {
// override/add rules settings here, such as:
// "astro/no-set-html-directive": "error"
}
}
]
}


File: src/html/.tsconfig.json

{
"root": true,
"extends": "astro/tsconfigs/strict"
}


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

You can find our supported range of browsers and their versions in our .browserslistrc file:

text
# 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

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

File: 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. 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 below.

{palette.map(c => (
{c.name}
))}

Try every colour, skin and widget set live on the Colors demo page β€” 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/[email protected]/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" 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 Colors demo and Theme Customize both have a palette toggle.

html
<!-- either the designed palette … -->
<link rel="stylesheet" href="css/adminlte-colors.css" />
<!-- … or the AdminLTE 3 originals, not both -->
<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:

Name Hex Text (v3 rule) White text
   
text
`{c.name}` | `{c.hex}` | {c.textColor === "white" ? "white" : "dark (#1f2d3d)"} | {c.contrastWhite.toFixed(1)}:1{c.textColor === "white" && c.contrastWhite < 4.5 ? " ⚠︎" : ""} | {c.note} |

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">
Colour Default (v3 rule) With aa
   {c.name} {c.hex} white text β€” {c.contrastWhite.toFixed(2)}:1 black text β€” {c.contrastBlack.toFixed(2)}:1

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 Colors demo 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).

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:

Skin Header Sidebar Primary v2/v3 name
{s.name} {s.header.cls} Β· {s.header.theme} {s.sidebar.cls} Β· {s.sidebar.theme} {s.primary} {s.note}

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.

Name Hex OKLCH White text Character & use
   
text
`{c.name}` | `{c.hex}` | {c.oklchLabel} | {c.contrastWhite.toFixed(1)}:1 | {c.description} {c.use} |

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

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) β€” 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
<!-- header -->
<nav class="app-header navbar navbar-expand text-bg-sky" data-bs-theme="dark">…</nav>

<!-- sidebar -->
<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 Colors demo applies any preset to itself with one click; the Theme Customize 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 => (

text
<h6 class="fw-semibold mb-1">{group.name}</h6>
{group.note}
Preview Skin Header Sidebar Primary Why
{s.name} {s.header.cls} Β· {s.header.theme} {s.sidebar.cls} Β· {s.sidebar.theme} {s.primary} {s.note}

))}

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.

Set Classes Why it works
{s.name} {s.colors.map(c => {c})} {s.why}

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 checker on the demo page 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:

text
Angle is hue, distance from the centre is chroma. Filled dots: the palette. Hollow rings: Bootstrap's *primary Β· success Β· danger Β· warning Β· info* (secondary sits unlabelled at the centre). The unlabelled dots near the centre are the five neutrals β€” navy, steel, slate, graphite, midnight.


<ol class="mb-0">
  <li class="mb-3">**Equal contrast, by construction.** Each chromatic colour is the *lightest* 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">**Hues in the gaps.** 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">**Chroma capped, neutrals tinted.** 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>

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.
  • 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 Skins 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), or use the designed palette with the alias one-liner $lte-palette-aliases: ("lightblue": "sky", "maroon": "pink");.


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

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 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 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; 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 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 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
<!-- Light page, dark sidebar (the demo pattern) -->
<body class="bg-body-tertiary">
  <aside class="app-sidebar bg-body-secondary shadow" data-bs-theme="dark">
    ...
  </aside>
</body>
html
<!-- Light page, light sidebar -->
<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">
  <!-- header, sidebar, main content with tighter spacing -->
</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.


File: 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; 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/[email protected]/dist/css/adminlte.min.css"
  integrity="sha384-PASTE_THE_HASH_HERE"
  crossorigin="anonymous"
/>

You can get the hash from the jsDelivr file detail page β€” 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>
  <!-- 1. Bootstrap Icons (font CSS) β€” load first so icons render with the page -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css" />

  <!-- 2. OverlayScrollbars (optional, for the sidebar scroller) -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/overlayscrollbars.min.css" />

  <!-- 3. AdminLTE β€” includes Bootstrap CSS via its imports -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/adminlte.min.css" />
</head>

<body>
  …

  <!-- Scripts at the END of body -->
  <!-- 4. Popper (Bootstrap dependency for dropdowns/tooltips/popovers) -->
  <script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>

  <!-- 5. Bootstrap -->
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>

  <!-- 6. OverlayScrollbars -->
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/browser/overlayscrollbars.browser.es6.min.js"></script>

  <!-- 7. AdminLTE β€” must load AFTER Bootstrap -->
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/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 or 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 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 β€” change colours, sidebar width, breakpoints before you deploy
  • Recommended Integrations β€” pick lightweight libraries for the things AdminLTE doesn't bundle
  • Accessibility β€” make sure your deployed pages keep their a11y guarantees

File: 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 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
<!-- AdminLTE 4 + Bootstrap 5.3 (CSS) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/overlayscrollbars.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/adminlte.min.css" />

And these just before ``:

html
<!-- Bootstrap + Popper + AdminLTE (JS) -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/browser/overlayscrollbars.browser.es6.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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:

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

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

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

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

  </div>
</body>

Open it in a browser. The hamburger button at the top-left collapses the sidebar β€” that's AdminLTE's PushMenu 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 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 β€” change colors, fonts, and spacing via SCSS variables or CSS custom properties.
  • Make it functional β†’ JavaScript Plugins β€” full reference for all seven plugins.
  • Make it dark β†’ Color Mode β€” light/dark/auto toggle.
  • Make it international β†’ RTL Support β€” right-to-left layouts for Arabic, Hebrew, Persian, etc.
  • Add real widgets β†’ Recommended Integrations β€” 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 guide for breaking changes.


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

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

    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 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 or
  • install the dependencies.
  • run npm run dev to start the server.

File: 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 is a lightweight, no-dependency date and time picker.

html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/flatpickr.min.css" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></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 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/[email protected]/dist/css/tom-select.bootstrap5.min.css" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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 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 demo page.

If you have to keep Select2, AdminLTE ships a styling-only compatibility sheet β€” see 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/[email protected]/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/[email protected]/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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 handles single, range, and multi-handle sliders with no dependencies.

html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/nouislider.min.css" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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 is a flat, modern color picker with multiple themes.

html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/[email protected]/dist/themes/classic.min.css" />
<script src="https://cdn.jsdelivr.net/npm/@simonwep/[email protected]/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 handles phone numbers, credit cards, dates, currency β€” anything with a fixed pattern.

html
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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 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/[email protected]/dist/dropzone.css" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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 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/[email protected]/dist/filepond.min.css" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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 is the best-supported open-source WYSIWYG editor. Lightweight, modular, no dependencies.

html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/quill.snow.css" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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 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/[email protected]/dist/easymde.min.css" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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 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 (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/[email protected]/dist/chart.umd.min.js"></script>

<!-- Give the canvas a parent with a height; the chart fills it and follows its width. -->
<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 to copy it into your own pages.

Data tables

Tabulator

Tabulator 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/[email protected]/dist/css/tabulator_bootstrap5.min.css" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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 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/[email protected]/skeleton.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fullcalendar/[email protected]/theme.css" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/all/global.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@fullcalendar/[email protected]/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 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/[email protected]/dist/jsvectormap.min.css" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jsvectormap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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 powers the kanban board demo. Reorder lists, drag between lists, touch-friendly. 31k stars.

html
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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>

GLightbox

GLightbox is a vanilla-JS lightbox for images, videos, and inline content.

html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/glightbox.min.css" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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 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 1,900+ Default, SVG, MIT
Lucide 1,500+ Modern, SVG, ISC, very consistent style
Tabler Icons 5,800+ MIT, broadest free set available
Material Symbols 3,500+ Google, Apache 2.0, variable font
FontAwesome Free 2,000+ free Most-recognized, font-based
Phosphor 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.


File: 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 β€” 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 ``. No install, no build step:

html
<!-- In <head> -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/overlayscrollbars.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/adminlte.min.css" />

<!-- Before </body> -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/browser/overlayscrollbars.browser.es6.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/adminlte.min.js"></script>

Then copy any demo page HTML into your own file. See Getting Started for the minimum-viable layout.

npm + bundler

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

bash
npm install [email protected]

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 & Theming 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 or earlier.

Where to next

Releases and changelog

Always grab the latest from the GitHub releases page β€” that's where the changelog lives.

Legacy releases:
AdminLTE 3.2 Β·
AdminLTE 2 Β·
AdminLTE 1


File: 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 ``.

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 `` 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">
        <!-- nav items -->
      </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">
    <!-- page title + breadcrumb -->
  </div>
  <div class="app-content">
    <!-- page body, cards, tables, charts, etc. -->
  </div>
</main>

.app-content-header is the title strip β€” typically an <h3> and a breadcrumb in a row. .app-content is everything below it. Both should wrap their children in .container-fluid (or .container for capped width).

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 `` to pin it to the bottom of the viewport.

Body-level layout modifiers

Layout behaviour is mostly controlled by classes on ``, 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)

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:

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 β€” 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.
  • 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


File: src/html/components/docs/license.mdx

AdminLTE is an open source project that is licensed under the MIT license. 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).

What You Are Allowed To Do With AdminLTE
  • 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.

File: 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 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 ``) 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 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 Tom Select Search, multi-select, tagging, remote data, virtual scrolling. See the API mapping and the Select2 compatibility theme if you must keep Select2.
Bootstrap4 Duallistbox Tom Select A multiple Tom Select covers the same job with less UI.
DataTables Tabulator Sorting, filtering, pagination, inline edit, export. Used in tables/data.html.
bootstrap-datepicker Flatpickr One library replaces all four v3 date/time plugins.
daterangepicker Flatpickr (mode: "range")
Bootstrap Timepicker Flatpickr (noCalendar: true)
tempusdominus Flatpickr
Summernote Quill Snow + bubble themes demoed in forms/editors.html.
bootstrap4-wysihtml5 Quill
SimpleMDE EasyMDE EasyMDE is the maintained SimpleMDE fork β€” same API.
bootstrap-slider noUiSlider
iCheck / icheck-bootstrap Native .form-check Bootstrap 5.3's own checkboxes and switches need no plugin.
bootstrap-colorpicker Pickr Or the native ``.
jQuery InputMask IMask
jQuery Knob Chart.js 4 doughnut A doughnut with circumference/cutout and a centre label β€” see the radial bar on charts/chartjs.html.
jQuery Mapael jsVectorMap
jQuery UI sortable SortableJS Powers the kanban demo.
fullCalendar (jQuery build) FullCalendar 7 Same project β€” v5+ dropped jQuery. Used in pages/calendar.html.
Chart.js 2 Chart.js 4 Chart.js was never jQuery-based; just a major bump. Used in the dashboards and charts/chartjs.html.
dropzone Dropzone 6 or FilePond v6 is a plain-JS rewrite.
Ekko Lightbox GLightbox
jQuery Overlay Scrollbars Native scrollbar-* CSS AdminLTE 4 styles the sidebar scrollbar in CSS.
toastr / SweetAlert2 Bootstrap toasts / SweetAlert2 SweetAlert2 has been jQuery-free since v7.
jsGrid Tabulator

Install snippets and wiring code for each replacement live on the Recommended Integrations page.

Dark mode

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

html
<!-- AdminLTE 3 -->
<body class="dark-mode">

<!-- AdminLTE 4 -->
<html data-bs-theme="dark">

The included Color Mode widget reads/writes this attribute and persists the user's choice in localStorage. See the Color Mode 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
<!-- AdminLTE 3 -->
<div class="card bg-navy">…</div>
<aside class="main-sidebar sidebar-dark-primary">…</aside>

<!-- AdminLTE 4 β€” load css/adminlte-colors.css after adminlte.css -->
<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 page. For retheming Bootstrap's own colours, override --bs-primary etc. on :root; see Customization.

Icons

AdminLTE 3 demos used FontAwesome 6 free. AdminLTE 4 standardises on Bootstrap Icons β€” they're SVG, lighter, MIT-licensed, and ship with Bootstrap's ecosystem.

html
<!-- AdminLTE 3 -->
<i class="fas fa-home"></i>

<!-- AdminLTE 4 -->
<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 `` classes. See Recommended Integrations 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 β€” 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 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


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

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

        <li class="nav-header">MAIN</li>
        <li class="nav-item">
          <a href="/" class="nav-link active">
            <i class="nav-icon bi bi-speedometer"></i>
            <p>Dashboard</p>
          </a>
        </li>

        <li class="nav-item">
          <a href="#" class="nav-link">
            <i class="nav-icon bi bi-folder"></i>
            <p>
              Projects
              <span class="badge text-bg-info ms-2">12</span>
              <i class="nav-arrow bi bi-chevron-right"></i>
            </p>
          </a>
          <ul class="nav nav-treeview">
            <li class="nav-item">
              <a href="#" class="nav-link"><i class="nav-icon bi bi-circle"></i><p>Active</p></a>
            </li>
            <li class="nav-item">
              <a href="#" class="nav-link"><i class="nav-icon bi bi-circle"></i><p>Archived</p></a>
            </li>
          </ul>
        </li>

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

      </ul>
    </nav>
  </div>
</aside>

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 `` 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>
          <!-- rows -->
        </tbody>
      </table>
    </div>
  </div>
</div>

For interactive sorting/filtering/pagination, use 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 `` 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.

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

html
<!-- Rendered per request from the active locale -->
<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>
  <!-- repeat 3 more times with different values + colours -->
</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>
  <!-- repeat -->
</div>

The timeline component lives in _timeline.scss and has variants. See /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.

html
<div class="card direct-chat direct-chat-primary">
  <div class="card-header">
    <h3 class="card-title">Direct Chat</h3>
    <div class="card-tools">
      <span class="badge text-bg-primary">3 new</span>
      <button type="button" class="btn btn-tool" data-lte-toggle="chat-pane" title="Contacts">
        <i class="bi bi-people-fill"></i>
      </button>
    </div>
  </div>
  <div class="card-body">
    <div class="direct-chat-messages">
      <div class="direct-chat-msg">
        <div class="direct-chat-infos clearfix">
          <span class="direct-chat-name float-start">Olivia</span>
          <span class="direct-chat-timestamp float-end">12:05</span>
        </div>
        <img class="direct-chat-img" src="/avatars/olivia.jpg" alt="" />
        <div class="direct-chat-text">Hey, ready for the review?</div>
      </div>
      <div class="direct-chat-msg right">
        <div class="direct-chat-infos clearfix">
          <span class="direct-chat-name float-end">You</span>
          <span class="direct-chat-timestamp float-start">12:06</span>
        </div>
        <img class="direct-chat-img" src="/avatars/me.jpg" alt="" />
        <div class="direct-chat-text">Yes, on my way.</div>
      </div>
    </div>
    <div class="direct-chat-contacts">
      <!-- contacts list, shown when chat-pane toggled -->
    </div>
  </div>
  <div class="card-footer">
    <form>
      <div class="input-group">
        <input type="text" class="form-control" placeholder="Message…" />
        <button type="submit" class="btn btn-primary">
          <i class="bi bi-send"></i>
        </button>
      </div>
    </form>
  </div>
</div>

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, Accessibility, 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/[email protected]/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>, `` 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 `` 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