jeffersongoncalves/filament-panel-theme-isolation
Isolate Filament panel themes so each panel renders its own styles without leaking into others. Keep multiple panels visually independent, avoid CSS conflicts, and simplify theme customization in one lightweight package.
Install the plugin via Composer and register it on each Filament panel that needs its own theme preference:
composer require <vendor>/filament-panel-theme-isolation
use Vendor\PanelThemeIsolation\PanelThemeIsolationPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->id('admin')
->plugin(PanelThemeIsolationPlugin::make());
}
Where to look first:
src/ directory for the panel-ID-to-storage-key derivation — this tells you exactly what namespacing strategy is used and whether it survives panel renames.php artisan vendor:publish --tag=filament-panel-theme-isolation-config) for a prefix override or key format setting.resources/js/) — the actual localStorage read/write happens client-side, so the PHP side is only wiring.First use case to validate: put two panels on the same domain (e.g. /admin and /app), set one to dark and one to light, hard-refresh both, and confirm the preferences hold independently. That single test exercises the whole reason the package exists.
Register per panel, not globally. The plugin's value is derived from the panel it is attached to. If you register it via a shared base panel class, make sure each concrete panel passes its own ->id(); a plugin that keys off a missing or duplicated ID gives you silent collisions.
Pair with explicit panel defaults. Isolation only controls the stored preference. Combine with Filament's own ->defaultThemeMode() so a first-time visitor gets a sensible theme before any localStorage entry exists:
$panel->id('admin')
->defaultThemeMode(ThemeMode::Dark)
->plugin(PanelThemeIsolationPlugin::make());
Tenant-aware panels. For multi-tenant apps where the same panel ID serves multiple tenants on one domain, the panel ID alone isn't enough of a discriminator. Check whether the plugin exposes a key prefix or resolver hook; if it does, fold the tenant key into it. If it doesn't, isolate by domain/subdomain instead of relying on this package.
Browser tests are your regression net. Theme preference is client state, so Pest/Dusk or Playwright coverage beats unit tests here. A minimal assertion: visit panel A, toggle dark, visit panel B, assert document.documentElement class or color-scheme is still light, then reload panel A and assert dark persisted.
Deployment ordering. Because the plugin ships frontend assets, run php artisan filament:assets (and your usual filament:upgrade) as part of deploy. Adding the plugin to a panel without re-publishing assets is the most common "it does nothing" report.
localStorage is origin-scoped, not path-scoped. Every panel on example.com shares one storage bucket. Isolation is entirely achieved by the key prefix — so uniqueness of panel IDs is a hard correctness requirement, not a convention.
Renaming a panel ID orphans preferences. Changing ->id('admin') to ->id('backend') changes the derived key, and users silently fall back to the panel default. Treat panel IDs as data, not refactoring trivia.
Expect a flash of the wrong theme. localStorage is read after HTML parse. If you see a dark-to-light flicker, apply the stored value in an inline head script or color-scheme meta before paint rather than waiting on Filament's bundle.
Check interaction with Filament's own dark-mode toggle. Filament writes its own key in some configurations. Two writers to overlapping keys produce a toggle that appears to "revert" on navigation. Verify the plugin fully owns the key, or that it defers to Filament's.
No server-side persistence. Preferences do not follow the user across browsers or devices, and are lost on storage clear or private browsing. If users expect their theme to stick across devices, that is a user-profile column, not this package.
Config caching. If the plugin reads a config prefix, remember php artisan config:cache in deploy — a changed prefix with a stale config cache is a confusing half-applied state (some users isolated, some not).
Version pinning. This is a 1.0.0 initial release. Pin it ("^1.0") and read the upgrade notes on every minor. It targets Filament v3 only — an eventual Filament v4 upgrade will require the plugin to be updated first, so note it as a blocker in your dependency upgrade checklist.
Debugging checklist when isolation appears broken: (1) confirm the plugin is registered on both panels; (2) inspect localStorage in devtools and verify two distinct keys exist; (3) confirm panel IDs differ; (4) confirm assets were republished after the last deploy. Those four cover the large majority of failure modes.
How can I help you explore Laravel packages today?