Migration
Coming from raw AdminLTE 4, the React port, or the Laravel edition.
adminlte-vue ships the same prebuilt AdminLTE 4 CSS, so your visual result is unchanged — what
changes is that imperative markup and data-lte-* attributes become declarative Lte* components
plus composables. This guide maps the most common patterns.
From raw AdminLTE 4
The page shell
Raw AdminLTE wires the topbar, sidebar, content and footer by hand and toggles <body> classes
(layout-fixed, sidebar-expand-lg, sidebar-collapse, sidebar-mini, …) with adminlte.js.
LteDashboardLayout is the single host that replaces all of that: it renders the topbar/sidebar/
footer, provides the sidebar/color-mode/command-palette state, and reflects the body classes from a
watchEffect (SSR/hydration-safe). You pass the layout flags as props and the menu as data.
<script setup lang="ts">
import { LteDashboardLayout, LteAppContent } from 'adminlte-vue'
import type { MenuNode } from 'adminlte-vue'
const menu: MenuNode[] = [
{ type: 'header', text: 'MAIN' },
{ type: 'item', text: 'Dashboard', href: '/', icon: 'bi-speedometer' },
{
type: 'group',
text: 'Layout',
icon: 'bi-layout-wtf',
children: [{ type: 'item', text: 'Fixed', href: '/layout/fixed' }],
},
]
</script>
<template>
<LteDashboardLayout
:menu-items="menu"
brand-text="Acme"
sidebar-theme="dark"
:fixed-header="true"
:layout-fixed="true"
current-path="/"
>
<LteAppContent title="Dashboard">…</LteAppContent>
</LteDashboardLayout>
</template>
Body-class flags
The raw <body class="…"> toggles become LteDashboardLayout props:
| Raw body class | Prop | Default |
|---|---|---|
layout-fixed | layoutFixed | true |
fixed-header | fixedHeader | false |
fixed-sidebar | fixedSidebar | false |
fixed-footer | fixedFooter | false |
sidebar-mini | sidebarMini | false |
sidebar-expand-* | sidebarBreakpoint | 'lg' |
Extra static classes you can't express as props go through bodyClass / navbarClass /
sidebarClass. sidebarTheme is 'dark' by default; currentPath drives active-link detection
(replaces hand-maintained .active classes); initialColorMode is 'auto'.
Sidebar treeview + push-menu
Don't hand-write the <nav class="sidebar-menu"> tree or the nav-treeview open/close markup —
describe it as a MenuNode[] array and the layout renders the recursive sidebar for you. A
MenuNode is a discriminated union:
type | Fields |
|---|---|
'header' | text |
'item' | text, href, icon?, iconColor?, badge?, badgeColor?, target? |
'group' | text, icon?, iconColor?, badge?, badgeColor?, children |
The push-menu / collapse logic from push-menu.ts lives in the provided sidebar state. Anywhere
inside the layout you can drive it instead of dispatching data-lte-toggle="sidebar" clicks:
<script setup lang="ts">
import { useSidebar } from 'adminlte-vue'
const { isCollapsed, isMobile, toggle, collapse, expand } = useSidebar()
</script>
useSidebar() exposes isCollapsed, isMobileOpen, isMiniMode, isMobile (reactive breakpoint),
sidebarBreakpoint, and toggle / collapse / expand. toggle() opens the overlay on mobile and
collapses on desktop, matching AdminLTE. Set enableSidebarPersistence to persist the collapse state
under the lte.sidebar.state key.
Cards (collapse / maximize / remove)
The LteCard component ports card-widget.ts. Drop the data-lte-toggle="card-collapse"
buttons — toggle the tool buttons with boolean props instead:
<LteCard title="Sales" collapsible maximizable removable>…</LteCard>
| Prop | Type | Default |
|---|---|---|
title / icon | string | — |
theme | BootstrapTheme | — |
variant | 'default' | 'outline' | 'solid' | 'default' |
gradient | boolean | false |
collapsible | boolean | false |
defaultCollapsed | boolean | false |
maximizable | boolean | false |
removable | boolean | false |
For your own bespoke card markup, useCardWidget() returns the same state imperatively:
isCollapsed, isMaximized, isRemoved, collapse, expand, toggleCollapse, maximize,
minimize, toggleMaximize, remove.
Keeping raw markup working — useLteBehaviors
You don't have to convert everything at once. LteDashboardLayout installs useLteBehaviors(),
which delegates clicks for hand-written controls so they keep working without adminlte.js:
data-lte-toggle="card-collapse"/"card-maximize"/"card-remove"— finds the enclosing.cardand collapses / maximizes / removes it.data-lte-toggle="chat-pane"— toggles.direct-chat-contacts-openon the enclosing.direct-chat.- Bootstrap
.needs-validationforms get thewas-validated/checkValidity()submit handler.
So you can paste an existing AdminLTE card with its collapse button into a slot and it still works —
then migrate it to <LteCard collapsible> at your leisure.
Color mode
The dark/light switch persists under the lte-theme localStorage key and writes data-bs-theme on
<html>. Use <LteColorModeToggle> for the topbar glyph, or useColorMode() which returns
colorMode ('light' | 'dark' | 'auto'), resolvedMode (computed 'light' | 'dark') and
setColorMode(mode). In Nuxt the module injects a blocking head script that sets the attribute
before first paint to avoid a flash.
Bootstrap-driven JS
Dropdowns, modals, offcanvas and tooltips are still powered by Bootstrap's data-API. In plain Vue,
import bootstrap once in your entry; the Nuxt module does this in a .client plugin for you.
From the React port (adminlte-react)
The component surface mirrors adminlte-react one-to-one (same Lte* names, same AdminLTE CSS), so
migration is mostly idiomatic translation:
- React Context + hooks → Vue provide/inject + composables.
LteDashboardLayoutis the single provider;useSidebar()/useColorMode()/useCommandPalette()injectand throw if used outside it (the analog of calling a hook outside its provider). children→ default<slot />. Named render props become named slots (e.g.#header,#title,#tools,#footeronLteCard;#topbar-start/#topbar-end/#sidebar-brand/#logo/#footeron the layout).usePathname()is replaced by the explicitcurrentPathprop.- Routing is decoupled: pass a
linkComponentprop (default'a') instead of importing a<Link>, and anavigatecallback for the command palette.
From the Laravel edition (adminlte-laravel)
The Laravel edition renders Blade partials server-side. Here the same structure is component-driven:
- Blade layout includes /
@yield→LteDashboardLayout+ slots. - The PHP menu config array → a
MenuNode[]passed to:menu-items. The same array also feeds the ⌘K command palette viaflattenMenuToCommands()— no second menu definition. - Active-link detection done in Blade (
Request::is(...)) → thecurrentPathprop. route()/<a href>links → setlinkComponentto your router's link component (e.g.NuxtLink/RouterLink) so menu entries become SPA navigations.
Verifying the port
After migrating, the project's correctness gates are pnpm --filter adminlte-vue type-check and a
successful demo build:
pnpm --filter adminlte-vue type-check
pnpm build:demo