agroezinger/filament-navigation-enhanced
| Install | |
|---|---|
composer require agroezinger/filament-navigation-enhanced |
|
| Latest Version: | v1.0.3 |
| PHP: | ^8.2 |
| License: | MIT |
| Last Updated: | Sep 13, 2026 |
| Links: | GitHub · Packagist |
filament-navigation-enhanced
Adds collapsible child-item support to the Filament v5 sidebar navigation. A parent item becomes an expand/collapse toggle; its children are revealed in an animated sub-list. The group auto-expands whenever a child is active.
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.2 |
| filament/filament | ^5.0 |
Installation
Via Composer (Packagist)
composer require agroezinger/filament-navigation-enhanced
Publish the view override
The package ships a replacement for Filament's sidebar/item component. Publish it once so Filament picks it up:
php artisan vendor:publish --tag=navigation-enhanced-views
This copies the view to resources/views/vendor/filament-panels/components/sidebar/item.blade.php.
Usage
1. Register the plugin
Add NavigationEnhancedPlugin to every panel that should use the feature:
use Agroezinger\FilamentNavigationEnhanced\NavigationEnhancedPlugin;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugins([
NavigationEnhancedPlugin::make(),
]);
}
2. Register standalone parent items (no Page class needed)
Use parentNavigationItem() to declare a collapsible parent item directly on the plugin — no dummy Page or redirect required:
use Agroezinger\FilamentNavigationEnhanced\NavigationEnhancedPlugin;
use Filament\Navigation\NavigationItem;
NavigationEnhancedPlugin::make()
->parentNavigationItem(
NavigationItem::make('Parent Menu Item')
->icon('heroicon-o-arrow-up-tray')
->group('Workflows')
->sort(10)
->childItems([
NavigationItem::make('Sub-Menu-Item 1')
->url(fn() => ImportOrderPositionsPage::getUrl())
->isActiveWhen(fn() => request()->routeIs('filament.frontend.pages.sub-menu-item-1*')),
NavigationItem::make('Sub-Menu-Item 2')
->url(fn() => ImportPricelistPage::getUrl())
->isActiveWhen(fn() => request()->routeIs('filament.frontend.pages.sub-menu-item-2*')),
])
)
The child Pages must still opt out of standalone navigation registration:
protected static bool $shouldRegisterNavigation = false;
Call ->parentNavigationItem() multiple times to register more than one standalone parent.
3. Add the trait to a parent Page or Resource
use Agroezinger\FilamentNavigationEnhanced\Concerns\HasNavigationChildren;
use Filament\Navigation\NavigationItem;
class SettingsPage extends Page
{
use HasNavigationChildren;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-cog-6-tooth';
protected static string|\UnitEnum|null $navigationGroup = 'Einstellungen';
protected static ?string $navigationLabel = 'Einstellungen';
public static function getNavigationChildItems(): array
{
return [
NavigationItem::make('Allgemein')
->url(static::getUrl())
->icon('heroicon-o-cog-6-tooth')
->isActiveWhen(fn() => request()->routeIs('filament.club.pages.settings.general')),
NavigationItem::make('Abteilungen')
->url(DepartmentResource::getUrl('index'))
->icon('heroicon-o-rectangle-stack')
->isActiveWhen(fn() => request()->routeIs('filament.club.resources.departments.*')),
NavigationItem::make('Benutzer')
->url(UserResource::getUrl('index'))
->icon('heroicon-o-users')
->isActiveWhen(fn() => request()->routeIs('filament.club.resources.users.*')),
];
}
}
3. Suppress child items from the top-level navigation
Resources and Pages that appear as children should not register themselves as standalone sidebar items. Override shouldRegisterNavigation() on the Resource:
class DepartmentResource extends Resource
{
public static function shouldRegisterNavigation(): bool
{
return false;
}
}
For a child Page, set:
protected static bool $shouldRegisterNavigation = false;
4. Active-state detection
Use request()->routeIs() with a wildcard to match all pages of a resource (index, create, edit):
NavigationItem::make('Abteilungen')
->isActiveWhen(fn() => request()->routeIs('filament.{panel}.resources.{resource-slug}.*')),
For a single Page, match the exact route name:
NavigationItem::make('Allgemein')
->isActiveWhen(fn() => request()->routeIs('filament.{panel}.pages.{page-slug}')),
To find the exact route names for your panel, run:
php artisan route:list --name="filament.{panel}" --json \
| php -r "foreach(json_decode(file_get_contents('php://stdin'),true) as \$r) echo \$r['name'].PHP_EOL;"
Locked navigation items
Mark any NavigationItem as locked (e.g. a feature gated behind a subscription) to render a small
lock badge next to its icon — the package only renders the badge markup, the app supplies the
actual styling:
NavigationItem::make('Buchhaltung')
->url(AccountingEntryResource::getUrl('index'))
->icon('heroicon-o-banknotes')
->extraAttributes(['data-nav-locked' => 'true']),
Add the badge styling in your own CSS (the package intentionally ships no styles for it):
.fi-sidebar-item-icon-ctn-locked {
position: relative;
}
.fi-sidebar-item-icon-lock-badge {
position: absolute;
inset-block-end: -0.125rem;
inset-inline-end: -0.125rem;
width: 0.75rem;
height: 0.75rem;
border-radius: 9999px;
background-color: theme(colors.gray.400);
/* e.g. a lock icon as a mask-image, or any indicator you prefer */
}
This works for icons rendered as a raw <svg> (Heroicon/BackedEnum icons) — those elements do
not support ::before/::after in any browser, so a real sibling <span> is used instead of a
CSS pseudo-element.
How it works
The package publishes a custom sidebar/item Blade component that extends Filament's default one. When a NavigationItem carries child items (set via $item->childItems([...])), the component renders a <button> toggle instead of an <a> link, and wraps the children in an Alpine.js-powered <ul> with enter/leave transitions.
HasNavigationChildren::getNavigationItems() wraps the parent's items and calls childItems() on each with the return value of getNavigationChildItems(). The parent item auto-expands on load when $active || $activeChildItems is true.
Items without children fall through to Filament's original rendering path unchanged.
License
MIT