shllghst/nova-pill-filter
A Laravel Nova package that adds pill-style filters to resource listings. Active filters show as compact, clickable pills, letting users quickly apply, stack, and clear query filters without leaving the index view. Drop-in Nova filter class for any resource. Lightweight and configurable.
This is a micro-package: a single Nova Filter subclass plus a Vue component that renders the filter's options as toggleable pill buttons instead of Nova's default dropdown. Treat it as a 100-line snippet you happen to install via Composer, not as infrastructure.
Minimal steps:
composer.json for the laravel/nova constraint and check the Vue file for Vue.extend/beforeCreate (Vue 2) vs defineComponent/setup (Vue 3). If your Nova major doesn't match, stop here and copy the component instead of installing it.composer require shllghst/nova-pill-filter — but note the Composer name (shllghst/nova-pill-filter) differs from the repo owner (mathieufrh), so verify the name field in the package's composer.json and that the vendor/package actually resolves on Packagist. Then read three files: the filter class under src/, the Vue component under resources/js/, and the service provider (if any) for asset registration.php artisan nova:filter StatusFilter in your app, extend the package's filter class instead of Laravel\Nova\Filters\Filter, return your statuses from options(), and point component() at the package's pill component. You should see pills instead of a dropdown on that resource's index.The value proposition is real but narrow: pills make a 3–6 option filter one click instead of two, and they show current state at a glance. That's the whole assessment. If your filter has more than ~8 options, pills are worse than a dropdown, and you've gained nothing.
Extend, don't configure. The package almost certainly has no config file. The intended pattern is inheritance:
namespace App\Nova\Filters;
use Shllghst\NovaPillFilter\PillFilter; // verify namespace in src/
class StatusFilter extends PillFilter
{
public function name() { return 'Status'; }
public function options(Request $request)
{
return [
'Active' => 'active',
'Paused' => 'paused',
'Archived' => 'archived',
];
}
public function apply(Request $request, $query, $value)
{
return $query->where('status', $value);
}
}
The options() shape matters — most Nova pill implementations use the same label => value map as core Nova filters, so the Vue side can v-for over filter.options. Confirm this against the component; some forks pass an array of {label, value} objects via ->withMeta().
Multi-select via array values. The natural fit for pills is multi-select, which core Nova filters don't do well. Pattern: have the component emit an array, then in apply():
public function apply(Request $request, $query, $value)
{
$values = is_array($value) ? $value : [$value];
return $query->whereIn('status', $values);
}
The package may or may not support this out of the box. If the Vue component only emits a single scalar, you're either patching the component or dropping multi-select. Check props and $emit in the .vue file first — that's a five-minute read that determines your whole design.
"All" as a pill. A pill that clears the filter is the most-used pill on any real screen. If the component sets value = '' and your apply() returns $query unchanged on empty, Nova's own filter-clearing logic should handle it — but verify, because a pill that can't be un-toggled is the single most common complaint about this UI pattern.
Persistence and defaults. Nova filters persist in the session/URL per key(). If you copy the package's filter into several resources, override key() with a stable string per resource, or the same selection leaks between them. Use ->default() for the "Active" default on a status filter — but only if the component correctly renders the default as a selected pill on first load; otherwise the pill state and the actual query disagree, which is a confusing bug.
Asset pipeline. Nova packages that ship Vue components don't compile them — your app's webpack.mix.js / vite.config.js has to include nova-components or the package's resources/js. If the package ships a prebuilt dist/js/filter.js and registers it via Nova::script(), that's easier but likely broken on your Nova version. If it ships raw .vue only, you'll add an import in your app's resources/js/filters.js and run npm run dev/npm run build. Budget for this step; it's where most installs actually fail.
Cannot read properties of undefined in a Nova 4 app, and the error surfaces in the browser console, not in Laravel logs. Check the console first when pills don't render.shllghst/nova-pill-filter on Packagist, mathieufrh/nova-pill-filter on GitHub. Add a VCS repository entry or verify the Packagist entry is the same code. Typosquatting risk is non-trivial for abandoned-looking Nova packages.key(), which defaults to the class name. Two resources sharing the same filter class share state. Override key() per resource, e.g. return 'users-status';.?filter[StatusFilter]=active query string. When a pill click "does nothing," inspect the URL first: no param means the component isn't wired to Nova's Filter mixin (it must emit input and read from the filter prop); a param that's present but ignored means apply() isn't matching the value type (e.g. '0' string vs 0 int, or boolean serialized as "true").apply() receives '' (empty string) when the filter is cleared, not null. A whereIn('status', $values) with [''] returns nothing rather than everything — guard for it.#fff/#000, pills will look wrong in Nova's dark theme. Tailwind classes (bg-primary-500, text-gray-500) inherit Nova's theme; hardcoded hex doesn't..vue into resources/js/components/PillFilter.vue, register it, and delete the dependency. You get full control of multi-select, "All," counts, and styling, and you stop carrying an unmaintained Composer package for 50 lines of code. This is the recommended path for anything beyond a throwaway internal tool.How can I help you explore Laravel packages today?