Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Adminlte Symfony Laravel Package

colorlibhq/adminlte-symfony

Official AdminLTE 4 integration for Symfony 8.1+ and PHP 8.4+, featuring Bootstrap 5.3, AssetMapper, Twig components, a config-driven sidebar, SSR-safe dark mode, and an optional EasyAdmin theme. Build polished, responsive admin panels fast with an accessible UI that is easy to customize.

View on GitHub
Deep Wiki
Context7

Getting Started

The honest first step: this package will not install in a Laravel app. It is a Symfony bundle (AbstractBundle, Flex recipe, config/packages/*.yaml, Twig Components via symfony/ux-twig-component). If you composer require it into Laravel you get a bundle class nothing autoloads and a hard requirement on symfony/framework-bundle 8.1+. There are three legitimate ways a Laravel developer "uses" this:

  1. Use the sibling port. Colorlib ships colorlibhq/adminlte-laravel from the same monorepo family, with the same component inventory and the same config-driven sidebar. That is your day-to-day answer for actually shipping.
  2. Steal the design. Read this repo's templates/, templates/components/, and the config schema, then re-implement in Blade. The markup and class names are the real value.
  3. You're a Laravel dev who inherited/will inherit a Symfony admin. Then the setup steps below apply verbatim.

Minimal setup if you are in the Symfony codebase:

composer require colorlibhq/adminlte-symfony
# Flex registers AdminLteBundle automatically; otherwise add to config/bundles.php

Then create the config file — and be careful with the filename/spelling, see Gotchas — and define the menu:

# config/packages/admin_lte.yaml
admin_lte:
    title: 'Ops'
    color_mode: auto
    dashboard_route: app_dashboard
    menu:
        - { label: 'Dashboard', route: 'app_dashboard', icon: 'bi bi-speedometer2' }
        - { header: 'MANAGEMENT' }
        - { label: 'Users', route: 'app_user_index', icon: 'bi bi-people', permission: 'ROLE_ADMIN' }

Then one controller and one template:

#[Route('/admin', name: 'app_dashboard')]
public function dashboard(): Response
{
    return $this->render('admin/dashboard.html.twig');
}
{% extends '@AdminLte/layout.html.twig' %}
{% block page_title %}Dashboard{% endblock %}
{% block body %}
    <twig:Adminlte:SmallBox title="150" text="New Orders" icon="bi bi-bag" url="#" />
{% endblock %}

Where to look first, in this order: templates/demo/dashboard.html.twig (the only end-to-end example), templates/layout.html.twig (tells you exactly which blocks are overridable and where the CDN tags live), the config example in the README, then the changelog — which is unusually informative and doubles as a bug list.

First real use case: an internal back-office (users, orders, a couple of CRUD screens) where you want a sidebar, cards, and dark mode without a Node build. That is precisely the bundle's sweet spot: the CDN defaults mean you can render a dashboard in about fifteen minutes.

Implementation Patterns

Treat the config menu as the single source of truth. This is the package's best idea and the one worth copying into Laravel. One nested array drives the sidebar, the active-route highlighting, the ⌘K CommandPalette index, and per-item authorization. In Laravel the direct analogue is a config/navigation.php plus a Menu service and a View::composer/Blade component, rather than hand-writing <x-nav-link> calls on every page:

// app/Support/Menu.php — Laravel port of the same contract
public function items(): array
{
    return collect(config('navigation.menu'))
        ->map(fn ($i) => [...$i, 'active' => $this->isActive($i)])
        ->filter(fn ($i) => ! isset($i['permission']) || Gate::allows($i['permission']))
        ->all();
}

Keep the item keys identical (label, header, route, route_params, url, target, icon, badge, badge_class, permission, children) so documentation and muscle memory transfer between the Symfony and Laravel ports.

Extend, don't include, for the shell. The layout is a Twig parent template with blocks (page_title, body, stylesheets, javascripts), while everything inside the page is a component. In Blade that maps cleanly to @extends('layouts.admin') + @section for the chrome and <x-card>/<x-small-box> for content. Resist the urge to make the shell a component — you lose block-level override points for <head> injection.

Components are prop-slot, not data-driven. <twig:Adminlte:Card title="…" variant="primary" outline collapsible>…</twig:Adminlte:Card> — the Card renders its own header and you supply the body as slot content. Same for Timeline + TimelineItem, Accordion + AccordionItem. Note SidebarNavItem is explicitly recursive, which is why nested children in the config work without you writing a loop for each depth level. In Blade, recursive components need @props discipline and a $depth guard; in Laravel the easiest path is a view partial with @include('partials.nav-item', ['item' => $child]).

Dark mode: the flash-free script is the important artifact. It lives in templates/components/layout/_color_mode_script.html.twig and must execute before first paint — set data-bs-theme on <html>, read localStorage, fall back to prefers-color-scheme. In Laravel this means an inline <script> in <head> before @vite and before any deferred CSS:

<script>
 (() => {
   const s = localStorage.getItem('lte-theme') ?? 'auto';
   const d = s === 'auto' ? (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : s;
   document.documentElement.setAttribute('data-bs-theme', d);
 })();
</script>

The bundle also exposes window.adminlteToggleColorMode() and ships a Stimulus controller (assets/controllers/color-mode_controller.js) as an alternative — pick one, not both, or your toggle will double-fire.

Asset pipeline: CDN for dev, AssetMapper for prod — and never both. The layout's stylesheets/javascripts blocks emit pinned [email protected] CDN tags so the bundle works with zero config. For production you override those blocks in your base template to emit {{ importmap('app') }} instead. In Laravel the equivalent is @vite(['resources/js/app.js']) and a resources/js/app.js that does import '@colorlibhq/adminlte-symfony/adminlte.js' — but note that the Symfony package's JS entry is only meaningful in its own asset graph; for Laravel you'd import 'admin-lte' from npm directly.

EasyAdmin theme = styling overlay, not a shell. The shipped templates/bundles/EasyAdminBundle/layout.html.twig layers AdminLTE CSS/JS onto EasyAdmin's own layout. The README is explicit that a restructured AdminLTE shell for EasyAdmin is "being rolled out incrementally". If you're the Laravel equivalent (Filament, Nova, Backpack), read this as "don't expect a menu-config takeover" — you'll get colours and font, not the sidebar.

Gotchas and Tips

The config filename in the README is inconsistent, and only one spelling works. The prose says config/packages/adminlte.yaml in one place and config/packages/admin_lte.yaml in the Configuration section. The working key is admin_lte (Symfony derives it from AdminLteBundle), so the file must be admin_lte.yaml. A wrong filename doesn't always fail loudly — you'll get a default-configured layout with an empty sidebar and spend an hour on it.

Pin your expectations to 0.2.x. Four stars, zero dependents, and the 0.2.1 changelog documents that the EasyAdmin theme had never rendered before that release — it infinitely recursed into itself (fixed with @!EasyAdmin/layout.html.twig) and its JS block was silently dead because the override declared body_javascripts while EasyAdmin renders body_javascript. That's the maturity signal: the core layout/components are exercised by the demo, the integrations are not. Budget review time for anything beyond layout.html.twig + widgets.

Requirements are bleeding edge. PHP 8.4+, Symfony 8.1+. The changelog mentions 7.4 LTS support "once stabilised", i.e. not yet. Also note PHPUnit 13 (dev-only) needs PHP >= 8.4.1 while the package floor is 8.4.0 — a contributor on exactly 8.4.0 hits a wall. For a Laravel team this is mostly moot, but it tells you the maintenance window is narrow and recent.

Route typos in the menu fail silently at runtime. Nothing validates that route: 'app_user_index' exists when the container boots; Twig's path() throws only when that item renders, and if the item is behind a permission you may never see the error locally. Add a smoke test that resolves every menu route through the router:

public function testMenuRoutesExist(): void
{
    foreach ($this->flatten($this->menu()) as $item) {
        if (isset($item['route'])) {
            $this->assertNotNull($this->router->getRouteCollection()->get($item['route']), $item['route']);
        }
    }
}

permission is a no-op without symfony/security-bundle. The README notes this in passing. If you're prototyping without security installed, every item renders regardless of role and you'll ship a leak. Same lesson for Laravel: make the Gate check unconditional and have it deny by default when auth is absent.

AssetMapper + Bootstrap double-loading. importmap:require bootstrap admin-lte pulls Bootstrap in twice if admin-lte already bundles it (or if you leave the CDN <link> in the stylesheets block while also emitting importmap('app')). Symptoms are subtle — doubled modal backdrops, tooltips offset by a few pixels, data-bs-theme appearing to half-work. Verify by grepping the rendered HTML for bootstrap.min.css and adminlte.min.css count; there should be exactly one of each, from exactly one source.

Twig silently drops blocks the parent never renders. The body_javascripts bug is a general hazard: a {% block %} you define that the parent doesn't {% block %} in its own body simply vanishes with no warning. When something "doesn't load", don't debug the asset — debug whether the block name is right. bin/console debug:twig --filter=layout and a temporary {{ dump(_self.getBlocks()) }} save time.

Palette opt-ins are additive stylesheets, not config. Since core 4.4–4.8 you can add dist/css/adminlte-colors.css (14 extended colours), dist/css/adminlte-colors-v3.css (the 18 AdminLTE 3 colours), data-lte-primary="teal" on <html> to promote a palette colour to Bootstrap primary, data-lte-print="plain" for printable pages, and data-lte-contrast="aa" for WCAG AA on the v3 palette. None of these have a config/packages/admin_lte.yaml equivalent — you add them to the stylesheets block or your <html> attributes yourself. Don't go looking for a config key.

Override templates, don't fork the bundle. Symfony's templates/bundles/AdminLteBundle/… path shadows the bundle's own templates, and inside a bundle template @!AdminLte/… (! = the bundle's own copy) prevents self-recursion. That's the mechanism behind the EasyAdmin fix, and it's the extension point you'll use most: shadow layout.html.twig, swap CDN tags for Vite/importmap, keep everything else upstream.

The CommandPalette is only as good as your menu. ⌘K is indexed from the sidebar config, so items hidden by permission — and items you nested three levels deep with no route — are the ones users will complain are "missing". Flatten deliberately if you want deep pages searchable.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log