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 classPropDefault
layout-fixedlayoutFixedtrue
fixed-headerfixedHeaderfalse
fixed-sidebarfixedSidebarfalse
fixed-footerfixedFooterfalse
sidebar-minisidebarMinifalse
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'.

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:

typeFields
'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>
PropTypeDefault
title / iconstring
themeBootstrapTheme
variant'default' | 'outline' | 'solid''default'
gradientbooleanfalse
collapsiblebooleanfalse
defaultCollapsedbooleanfalse
maximizablebooleanfalse
removablebooleanfalse

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 .card and collapses / maximizes / removes it.
  • data-lte-toggle="chat-pane" — toggles .direct-chat-contacts-open on the enclosing .direct-chat.
  • Bootstrap .needs-validation forms get the was-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. LteDashboardLayout is the single provider; useSidebar() / useColorMode() / useCommandPalette() inject and 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, #footer on LteCard; #topbar-start / #topbar-end / #sidebar-brand / #logo / #footer on the layout).
  • usePathname() is replaced by the explicit currentPath prop.
  • Routing is decoupled: pass a linkComponent prop (default 'a') instead of importing a <Link>, and a navigate callback 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 / @yieldLteDashboardLayout + slots.
  • The PHP menu config array → a MenuNode[] passed to :menu-items. The same array also feeds the ⌘K command palette via flattenMenuToCommands() — no second menu definition.
  • Active-link detection done in Blade (Request::is(...)) → the currentPath prop.
  • route() / <a href> links → set linkComponent to 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