emuniq/filament-collapsible-subnav
A Filament v3, v4, and v5 plugin that adds a collapsible toggle to sub-navigation sidebars on resource pages. It auto-registers to all panels with zero configuration and works with both top and sidebar navigation layouts.
Install is genuinely one command. No config publish, no service provider edit, no panel wiring required.
composer require emuniq/filament-collapsible-subnav
php artisan optimize:clear
The plugin auto-registers against every panel in the application. Confirm it booted by viewing source on a Filament page and looking for the inline critical CSS/script block in <head>. That block is the FOUC-prevention mechanism — if it's absent, nothing else will work.
The one prerequisite that gates everything: the page must render a sub-navigation sidebar. That means a Resource or Page whose sub-nav position is Start, not End or Top.
// Filament v3
protected static SubNavigationPosition $subNavigationPosition = SubNavigationPosition::Start;
// Filament v4 / v5
public static function getSubNavigationPosition(): SubNavigationPosition
{
return SubNavigationPosition::Start;
}
If the toggle is missing, check this before anything else. The plugin deliberately does not touch the main panel sidebar — it only targets the secondary nav column inside resource pages.
First use case to validate: a resource detail page with enough tabs that the sub-nav column is eating content width — realistically 6+ tabs on a 1280–1440px viewport. Open it, click the toggle, confirm the subnav_collapsed cookie appears in DevTools, then hard-reload and verify there is no flash of the expanded state. That flash test is the entire reason to take a dependency here rather than writing twenty lines of CSS yourself — validate it in your real stack (behind your CDN, with your minifier), not on php artisan serve.
Where to look in vendor code first: the inline <head> injection and the livewire:navigated listener. Those two files are the only non-obvious logic in the package; everything else is CSS selectors. Read them both before you decide whether fork risk is acceptable.
Auto-registration means composer require silently changes every panel in the app. On a shared codebase, make it a reviewable diff instead:
use Emuniq\FilamentCollapsibleSubnav\CollapsibleSubnavPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugin(CollapsibleSubnavPlugin::make());
}
The explicit call is a no-op if auto-registration already fired, so there's no double-render cost — but the diff makes the blast radius visible to reviewers.
SubNavigationPosition::Start as your feature flagYou don't need a config toggle. Flip sub-nav position per resource: pages with Start get the collapsible sidebar, everything else is untouched. That gives you granular rollout with zero extra code, and reframes the team conversation from "should we install this plugin" to "which resource pages should have a Start sub-nav" — a much easier question to answer per-page.
If your panel uses ->spa(), hand-rolled collapse code bound to DOMContentLoaded will desync after the first Livewire navigation: the class persists on stale nodes or vanishes entirely. The plugin re-applies collapsed state and tooltips on livewire:navigated:
public function panel(Panel $panel): Panel
{
return $panel
->spa()
->plugin(CollapsibleSubnavPlugin::make());
}
If you vendor or fork the package, preserve that listener — it's the majority of the non-trivial logic.
php artisan collapsible-subnav:install
npm run build
This folds the plugin CSS into your compiled theme and removes one inline block per response. Worth doing if you're strict about response size or run a CSP that dislikes inline styles. Skip it if you have a custom theme you don't want to rebuild — the plugin works identically without it.
"emuniq/filament-collapsible-subnav": "^1.6"
Single maintainer, ~10 stars, zero dependents. The realistic failure mode isn't a security incident — it's a silent visual regression after a Filament minor bump that a user notices first. Pin, and decide internally who watches for it.
Whatever your browser-test stack is (Dusk, Pest browser, Playwright), assert three things on one sub-nav page: the toggle exists, clicking it applies the collapsed class, and a reload keeps it collapsed. That single check is what stands between you and the quiet-regression failure mode described above.
Roll it out to one page, measure the baseline (tab click-through, or time-to-complete for that page's primary task), and re-measure after two weeks. If nobody ever expands the sidebar again, the useful finding isn't "nice toggle" — it's "this page has too many tabs and should be split."
Selector brittleness across Filament majors is the real maintenance cost. Filament v4 renamed .fi-sidebar-item-button to .fi-sidebar-item-btn, which silently killed tooltips until v1.6.0 — no error, no warning, just missing hover text. Assume the next major does the same. When you bump Filament, manually check four things on a collapsed sub-nav: tooltip on hover, hidden badges (.fi-sidebar-item-badge-ctn), correct button padding/centering, and aria-label presence. Budget this as recurring maintenance.
Tooltips differ by Filament version. v3 uses Tippy; v4 doesn't expose Tippy globally, so the plugin falls back to a CSS ::after tooltip plus the native title attribute. If you've themed tooltips elsewhere in your panel, the collapsed state may look subtly inconsistent on v4/v5. Check visually rather than assuming parity.
Cloudflare Rocket Loader can strip the early script and reintroduce FOUC. The plugin tags its inline script with data-cfasync="false" to survive Rocket Loader. If you still see a flash on reload in production, inspect the served HTML and confirm that attribute survived — HTML minifiers and some edge workers drop unknown attributes. Always test behind your actual CDN.
The cookie is domain-wide and single-named. subnav_collapsed is one value shared across every panel and every resource with a Start sub-nav. A user who collapses on Panel A gets collapsed state on Panel B, and a user who collapses one resource collapses all of them. Usually fine — occasionally surprising during a staged rollout. Clear the cookie if you're demoing expanded state.
No config file means no knobs. There's no published config, no width token, no callback hook. Customisation paths are limited to (a) CSS overrides in your own theme targeting the plugin's selectors, or (b) forking. Decide which you're willing to own before you need it, not after.
Collapsed mode is hidden below 768px. On mobile the toggle and collapsed state are moot — sub-nav is already handled responsively by Filament. Don't file a bug when you can't reproduce on a phone.
Dark mode follows Filament's dark class, not prefers-color-scheme. If your panel toggles theme via a custom mechanism, verify tooltip and border styling in both modes on v4/v5 specifically.
Debugging checklist, in order: (1) is sub-nav position Start? (2) is the <head> inline block present in view-source? (3) does the subnav_collapsed cookie get set on click? (4) if SPA mode, does the class survive navigation? (5) does the inline script still carry data-cfasync="false" in production HTML? Each step isolates a different layer — server registration, head injection, JS handler, navigation lifecycle, CDN — and the answers narrow the problem fast.
How can I help you explore Laravel packages today?