hayderhatem / filament-sub-navigation
Add hover-based sub-navigation to Filament navigation items using badges
Requires
- php: ^8.1
- filament/filament: ^3.0
- illuminate/contracts: ^10.0|^11.0
- spatie/laravel-package-tools: ^1.15.0
Requires (Dev)
- nunomaduro/collision: ^7.9|^8.0
- orchestra/testbench: ^8.0|^9.0
- pestphp/pest: ^2.1
- pestphp/pest-plugin-arch: ^2.0
- pestphp/pest-plugin-laravel: ^2.0
README
Add beautiful dropdown sub-navigation menus to your Filament sidebar navigation items with just one trait!
✨ Features
- 🎯 Simple Setup - Just add one trait to your Resource
- 🎨 Beautiful UI - Seamlessly integrates with Filament's design
- 🌙 Dark Mode Support - Automatically adapts to light/dark themes
- 📱 Mobile Responsive - Works perfectly on all screen sizes
- ⚡ Alpine.js Compatible - Optional Alpine.js component included
- 🔄 Livewire Ready - Handles dynamic content updates
- 🐛 Debug Friendly - Comprehensive console logging
📸 Preview
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 │
│ │
└──────────────────────────────┘
🚀 Installation
Install the package via Composer:
composer require hayderhatem/filament-sub-navigation
The package will auto-register its service provider.
📋 Quick Start
Step 1: Add the Trait
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 }
Step 2: Configure Navigation
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() ), ]; }
Step 3: Define Sub-Navigation Items
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'), ], ], ]; }
Step 4: Register Sub-Navigation Data
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.
🔧 Advanced Usage
Multiple Resources with Sub-Navigation
You can add sub-navigation to multiple resources:
// In ProductResource.php class 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.php class 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', ], ], ]; } }
Using Alpine.js Component (Optional)
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" />
Custom Styling
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; }
🐛 Troubleshooting
Navigation Items Not Showing Dropdowns
- Check Console Logs: Open browser developer tools and look for sub-navigation debug messages
- Verify Key Matching: Ensure the key in
getSubNavigationItems()
matches your navigation label (lowercase, alphanumeric only) - Clear Caches: Run
php artisan cache:clear && php artisan view:clear
Example Debug Output
=== SUB-NAVIGATION INITIALIZATION === Found navigation items: 5 Available sub-navigation data: {users: Array(5)} Processing nav item: Users ID: users Created dropdown for: Users 🟢 Showing dropdown: Users
Dropdown Not Positioned Correctly
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; }
Dark Mode Issues
The package auto-detects dark mode, but if you have custom theme switching, you can manually trigger reinitialization:
// After theme change if (window.initializeSubNavigation) { window.initializeSubNavigation(); }
🎨 Customization Options
Sub-Navigation Item Structure
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) ]
Navigation Key Generation
The package generates keys from navigation labels using this logic:
- Converts to lowercase
- Removes all non-alphanumeric characters
- Example: "User Management" becomes "usermanagement"
📚 API Reference
HasBadgeSubNavigation Trait
Methods
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 items
getSubNavigationItems()
Returns array of sub-navigation items grouped by navigation key.
Returns: array<string, array>
🔄 Changelog
v1.0.0
- Initial release
- Basic dropdown sub-navigation functionality
- Dark mode support
- Alpine.js component
- Comprehensive documentation
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
The MIT License (MIT). Please see License File for more information.
🙏 Credits
- Hayder Hatem - Creator and maintainer
- Filament Team - For the amazing admin panel framework
- Laravel Community - For continuous inspiration
Made with ❤️ for the Filament community