Add beautiful dropdown sub-navigation menus to your Filament sidebar navigation items with just one trait!
When you hover over a navigation item with sub-navigation, a beautiful dropdown appears:
┌─ 👥 Users ▼ ─────────────────┐│ ││ 📋 All Users ││ ➕ Create User ││ 🗃️ User Categories ││ 📊 User Reports ││ ⚙️ User Settings ││ │└──────────────────────────────┘
Install the package via Composer:
composer require hayderhatem/filament-sub-navigation
The package will auto-register its service provider.
Add the HasBadgeSubNavigation
trait to any Filament Resource:
<?php namespace App\Filament\Resources; use Filament\Resources\Resource;use HayderHatem\FilamentSubNavigation\Concerns\HasBadgeSubNavigation; class UserResource extends Resource{ use HasBadgeSubNavigation; // ... your existing resource code}
In your Resource, override the getNavigationItems()
method:
public static function getNavigationItems(): array{ return [ static::createBadgeNavigation( label: 'Users', icon: 'heroicon-o-users', url: static::getUrl('index'), isActiveWhen: fn (): bool => request()->routeIs([ 'filament.admin.resources.users.*' ]), badge: static::getNavigationBadge(), subItems: static::getSubNavigationItems() ), ];}
Add the getSubNavigationItems()
method to your Resource:
public static function getSubNavigationItems(): array{ return [ 'users' => [ // This key should match your navigation label (lowercased, alphanumeric only) [ 'label' => 'All Users', 'description' => 'View and manage all users', 'url' => static::getUrl('index'), ], [ 'label' => 'Create User', 'description' => 'Add a new user to the system', 'url' => static::getUrl('create'), ], [ 'label' => 'User Categories', 'description' => 'Manage user categories', 'url' => route('filament.admin.resources.categories.index'), ], [ 'label' => 'User Reports', 'description' => 'View detailed user analytics', 'url' => route('filament.admin.resources.reports.index'), ], [ 'label' => 'User Settings', 'description' => 'Configure user preferences', 'url' => route('filament.admin.resources.settings.index'), ], ], ];}
In your AdminPanelProvider.php
, add sub-navigation data registration:
<?php namespace App\Providers\Filament; use Filament\Http\Middleware\Authenticate;use Filament\Http\Middleware\DisableBladeIconComponents;use Filament\Http\Middleware\DispatchServingFilamentEvent;use Filament\Panel;use Filament\PanelProvider;use Filament\Support\Colors\Color;use Filament\Widgets;use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;use Illuminate\Cookie\Middleware\EncryptCookies;use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;use Illuminate\Routing\Middleware\SubstituteBindings;use Illuminate\Session\Middleware\AuthenticateSession;use Illuminate\Session\Middleware\StartSession;use Illuminate\View\Middleware\ShareErrorsFromSession; class AdminPanelProvider extends PanelProvider{ public function panel(Panel $panel): Panel { return $panel ->default() ->id('admin') ->path('admin') ->login() ->colors([ 'primary' => Color::Amber, ]) ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') ->pages([ Pages\Dashboard::class, ]) ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') ->widgets([ Widgets\AccountWidget::class, Widgets\FilamentInfoWidget::class, ]) ->middleware([ EncryptCookies::class, AddQueuedCookiesToResponse::class, StartSession::class, AuthenticateSession::class, ShareErrorsFromSession::class, VerifyCsrfToken::class, SubstituteBindings::class, DisableBladeIconComponents::class, DispatchServingFilamentEvent::class, ]) ->authMiddleware([ Authenticate::class, ]) ->renderHook( 'panels::body.end', fn (): string => $this->getSubNavigationScript() ); } protected function getSubNavigationScript(): string { $script = ''; // Register sub-navigation data for each Resource that uses the trait $resources = [ \App\Filament\Resources\UserResource::class, // Add other resources that use HasBadgeSubNavigation here ]; foreach ($resources as $resourceClass) { if ( class_exists($resourceClass) && method_exists($resourceClass, 'getSubNavigationItems') ) { $subNavItems = $resourceClass::getSubNavigationItems(); if (!empty($subNavItems)) { foreach ($subNavItems as $id => $items) { $itemsJson = json_encode($items); $script .= "window.registerSubNavigation('{$id}', {$itemsJson});"; } } } } return $script ? '<script>' . $script . '</script>' : ''; }}
That's it! 🎉 Your sub-navigation dropdowns will now appear when hovering over navigation items.
You can add sub-navigation to multiple resources:
// In ProductResource.phpclass ProductResource extends Resource{ use HasBadgeSubNavigation; public static function getSubNavigationItems(): array { return [ 'products' => [ [ 'label' => 'All Products', 'url' => static::getUrl('index'), ], [ 'label' => 'Add Product', 'url' => static::getUrl('create'), ], [ 'label' => 'Categories', 'url' => route('filament.admin.resources.categories.index'), ], ], ]; }} // In OrderResource.phpclass OrderResource extends Resource{ use HasBadgeSubNavigation; public static function getSubNavigationItems(): array { return [ 'orders' => [ [ 'label' => 'All Orders', 'url' => static::getUrl('index'), ], [ 'label' => 'Pending Orders', 'url' => static::getUrl('index') . '?status=pending', ], [ 'label' => 'Completed Orders', 'url' => static::getUrl('index') . '?status=completed', ], ], ]; }}
For advanced customization, you can use the included Alpine.js component:
<x-filament-sub-navigation::alpine-sub-navigation :items="$subNavigationItems" trigger-selector=".my-nav-item"/>
The package automatically adapts to Filament's theme, but you can add custom CSS:
/* Custom dropdown styling */.sub-nav-dropdown { box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25) !important; border-radius: 12px !important;} /* Custom hover effects */.sub-nav-dropdown a:hover { transform: translateX(4px); transition: transform 0.2s ease;}
getSubNavigationItems()
matches your navigation label (lowercase, alphanumeric only)php artisan cache:clear && php artisan view:clear
=== SUB-NAVIGATION INITIALIZATION ===Found navigation items: 5Available sub-navigation data: {users: Array(5)}Processing nav item: Users ID: usersCreated dropdown for: Users🟢 Showing dropdown: Users
The package automatically handles positioning, but if you have custom CSS that affects the sidebar, you might need to adjust:
.fi-sidebar-item { position: relative !important;}
The package auto-detects dark mode, but if you have custom theme switching, you can manually trigger reinitialization:
// After theme changeif (window.initializeSubNavigation) { window.initializeSubNavigation();}
Each sub-navigation item supports these properties:
[ 'label' => 'Item Label', // Required: Display text 'description' => 'Item description', // Optional: Subtitle text 'url' => '/admin/some-path', // Required: Target URL 'icon' => 'heroicon-o-star', // Optional: Icon (future feature) 'badge' => '5', // Optional: Badge count (future feature)]
The package generates keys from navigation labels using this logic:
createBadgeNavigation()
Creates a navigation item with sub-navigation support.
Parameters:
string $label
- Navigation item labelstring $icon
- Heroicon namestring $url
- Target URLcallable $isActiveWhen
- Active state callbackstring|null $badge
- Badge textarray $subItems
- Sub-navigation itemsgetSubNavigationItems()
Returns array of sub-navigation items grouped by navigation key.
Returns: array<string, array>
Contributions are welcome! Please feel free to submit a Pull Request.
The MIT License (MIT). Please see License File for more information.
Made with ❤️ for the Filament community
Full-stack developer specializing in Laravel and Filament ecosystem development. Passionate about creating developer tools that simplify complex workflows and enhance user experience. Creator of innovative Filament packages that leverage AI technology to make data management more intuitive and accessible.