packstub/filament-nitpick
packstub/filament-nitpick is a small Filament plugin for Laravel that brings nitpick-style checks into your admin panel workflow. Lightweight and quick to install, it suits teams wanting fast feedback without leaving Filament.
Install the package and register the plugin on the Filament panel where users should pick themes:
composer require packstub/filament-nitpick
php artisan vendor:publish --tag="filament-nitpick-config"
use Packstub\FilamentNitpick\NitpickPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugin(NitpickPlugin::make());
}
Start with the user-menu Appearance item: by default the picker opens in a fixed-width modal, so the first useful check is switching between a couple of themes and confirming the panel reloads or restyles as expected. Look first at the published config, the ThemePicker API, and docs/theme-authors.md if you are building or integrating a theme. A minimal first use case is adding a Theme::make('press')->panel(fn (Panel $panel) => $panel->font('Inter')->colors([...])) entry so users can switch to a branded theme without editing panel code.
->picker() turns the picker on and accepts all options in one place. Options left out keep whatever an earlier call set:NitpickPlugin::make()->picker(
fn (ThemePicker $picker) => $picker
->columns(['default' => 2, 'lg' => 4])
->descriptions(),
condition: fn () => auth()->user()?->can('switch-themes'),
modal: false,
userMenuItem: false,
page: MyAppearancePage::class,
transition: Transition::Sweep,
);
Theme::make()->panel(). Use the callback for top navigation, fonts, render hooks, colours, and styling plugins. This is the supported place for panel-level configuration that should apply while the theme is active:Theme::make('press')
->panel(fn (Panel $panel) => $panel
->font('Inter')
->colors(['primary' => '#f97316'])
->renderHook('panels::body.start', fn () => view('themes.press.banner')));
Prefer ConfiguresPanel for reusable theme classes. If a theme is shared across projects, implement ConfiguresPanel instead of scattering panel callbacks through service providers. Keep pages, resources, and widgets out of this callback; those are routed before the request and are explicitly ignored and reported.
The Appearance page is still available. Set modal: false to link to the page instead of opening the modal. Use page: MyAppearancePage::class to customise the destination, and userMenuItem: false if the picker should only be reachable from your own UI.
Picker presentation is configurable at the plugin level. Shorten cards with ->swatches(false), show descriptions under the name with ->descriptions(), or fix modal columns with ThemePicker::make()->columns([...]). The fixed width/columns exist so cards do not jump when a theme changes the panel layout.
Programmatic picking is available. From your own scripts, call window.PackstubThemes.pick('theme-id') to switch with the normal transition. If the theme only shows after a page load, call pick('theme-id', { reload: true }) and then call PackstubThemes.reload() yourself once the choice is saved.
Theme authors can join the picker. Read docs/theme-authors.md for the Filament theme plugin contract. A theme plugin added via Theme::make()->plugin() is loaded only while that theme is active, so its boot() method and assets follow Filament’s normal plugin lifecycle.
Octane-safe by design. The plugin restores the panel at the end of the request, so a theme that configures the panel does not leak into the next Octane request. Still test with your Octane setup if you register global render hooks or assets from a theme plugin.
Pages, resources, and widgets cannot be added mid-request. If Theme::make()->panel() tries to register them, the package reports them and leaves them out because their routes already exist. Treat panel callbacks as styling and layout configuration only.
Themes that reconfigure the panel reload the page. Picking or leaving such a theme is not previewed half-applied. This is intentional; design your theme picker flow around a short reload rather than expecting a live preview. The transition covers the reload where possible.
Transition timing waits for page stability. The cover lifts after Livewire, the reopened modal, and lazy widgets settle, up to 2.5 s. If that feels slow on heavy panels, set transition: false; if you are scripting pick(), pass reload: true where needed and call PackstubThemes.reload() after persisting the theme.
Do not rely on deprecated picker methods. ->userSelectable() and ->appearancePage() still work but are deprecated and removed in 1.0. Also migrate isUserSelectable() and getAppearancePage() calls now; move all picker configuration into ->picker().
Theme plugin stylesheets and colours moved. A theme plugin’s stylesheets are no longer returned by the theme’s getStylesheets(), nor its colours by getColors(). Use the plugin API and Theme::make()->plugin() instead. filament:assets and filament:upgrade still publish plugin files.
Filament 5.10.2+ stores mode under filament:theme:mode. Nitpick writes both the new and old theme keys for compatibility. If your own code reads the mode key directly, check both.
Modal clipping is fixed for topbar filters. Themes that apply backdrop-filter, filter, or transform to the topbar previously clipped user-menu modals; the bar now drops those properties while a modal is open. If you support older versions, verify modal placement under such themes.
The light/dark switcher reflects the picked theme’s mode. Do not assume the page’s initial mode is what the picker applied—especially with themes like Midnight that force dark mode. Test both the user-menu switcher and the Appearance page after switching themes.
Debugging: watch the browser console for window.PackstubThemes availability, check reported ignored panel items when a theme’s callback registers pages/resources/widgets, and confirm the panel is restored after the request when running Octane.
How can I help you explore Laravel packages today?