Filters
Composable filter chips with field types, operators, and value editors — ideal for toolbars, tables, and data views.
Basic Usage
Pass a list of filterable fields. Users add filters from the menu; each active filter renders as a chip with a field label, an operator dropdown, a value editor, and a remove button:
no results found
Status
Priority
Assignee
<neura::filters
:fields="[
[
'key' => 'status',
'label' => 'Status',
'icon' => 'signal',
'type' => 'select',
'options' => [
['value' => 'active', 'label' => 'Active', 'color' => '#22c55e'],
['value' => 'inactive', 'label' => 'Inactive', 'color' => '#a1a1aa'],
],
],
[
'key' => 'priority',
'label' => 'Priority',
'type' => 'multiselect',
'options' => [
['value' => 'low', 'label' => 'Low'],
['value' => 'high', 'label' => 'High'],
],
],
['key' => 'name', 'label' => 'Name', 'type' => 'text'],
]"
:value="[
['field' => 'priority', 'operator' => 'is_any_of', 'values' => ['low', 'medium']],
]"
/>
Field Types
Three field types are supported. Select and multiselect use an options list; text uses a free-form input. Choosing
is empty
or is not empty
hides the value editor:
no results found
Status
Priority
Assignee
<neura::filters
:fields="$fields"
:value="[
['field' => 'status', 'operator' => 'is', 'values' => ['active']],
['field' => 'name', 'operator' => 'contains', 'values' => ['neura']],
['field' => 'assignee', 'operator' => 'not_empty', 'values' => []],
]"
/>
Default operators by type
| Property | Type | Default | Description |
|---|---|---|---|
| select | is, is_not, empty, not_empty | Default: is
|
|
| multiselect | is_any_of, is_not_any_of, includes_all, excludes_all, empty, not_empty | Default: is_any_of
|
|
| text | contains, not_contains, starts_with, ends_with, is, empty, not_empty | Default: contains
|
|
Option Colors and Icons
Options can include a
color
(shown as a dot) or an icon
(Heroicon name). Colors also appear on the chip when a single option is selected:
no results found
Status
<neura::filters
:fields="[
[
'key' => 'status',
'label' => 'Status',
'type' => 'select',
'options' => [
['value' => 'active', 'label' => 'Active', 'color' => '#22c55e'],
['value' => 'pending', 'label' => 'Pending', 'color' => '#f59e0b'],
],
],
]"
/>
Custom Trigger
Replace the default outline button with the
trigger
slot:
no results found
Status
Priority
Assignee
<neura::filters :fields="$fields">
<x-slot:trigger>
<neura::button variant="primary" size="sm" icon="plus">
Add filter
</neura::button>
</x-slot:trigger>
</neura::filters>
Single Filter per Field
With
:allow-multiple="false"
, fields already in use disappear from the add menu. Hide the menu search with :show-search="false"
:
no results found
Status
Priority
Assignee
<neura::filters
:fields="$fields"
:allow-multiple="false"
:show-search="false"
/>
Custom Operators
Override the operator list (and the default) per field:
no results found
<neura::filters
:fields="[
[
'key' => 'title',
'label' => 'Title',
'type' => 'text',
'operators' => ['contains', 'is', 'starts_with'],
'defaultOperator' => 'starts_with',
],
]"
/>
Listening for Changes
Every mutation dispatches
filters-changed
and syncs with wire:model
/ x-model
. Each filter is { id, field, operator, values }
:
no results found
Status
Priority
Assignee
<div x-on:filters-changed="console.log($event.detail.filters)">
<neura::filters :fields="$fields" />
</div>
<neura::filters :fields="$fields" wire:model.live="filters" />
<neura::filters :fields="$fields" name="filters" />
Livewire example
class Inbox extends Component
{
public array $filters = [];
public function getFilteredQuery()
{
$query = Message::query();
foreach ($this->filters as $filter) {
// $filter['field'], $filter['operator'], $filter['values']
}
return $query;
}
}
Disabled State
Disable all interactions with
:disabled="true"
:
no results found
Status
Priority
Assignee
<neura::filters :fields="$fields" :disabled="true" />
Props
| Property | Type | Default | Description |
|---|---|---|---|
| fields | array | [] | Required. Filterable field configurations |
| value | array | [] | Initial filters: ['field' =>, 'operator' =>, 'values' => []]
|
| label | string | translated | Label of the default trigger button |
| name | string|null | auto | Hidden input name (from wire:model
/ x-model
when omitted) |
| showSearch | boolean | true | Show the field search input in the add-filter menu |
| allowMultiple | boolean | true | Allow several active filters on the same field |
| showClear | boolean | true | Show the Clear all button when filters are active |
| disabled | boolean | false | Disable all interactions |
| trigger (slot) | slot | — | Custom element that opens the add-filter menu |
Field Configuration
| Property | Type | Default | Description |
|---|---|---|---|
| key | string | — | Required. Unique field identifier |
| label | string | title of key | Human-readable label |
| type | string | select | select
, multiselect
, or text
|
| icon | string|null | null | Heroicon shown next to the field label |
| options | array | [] | ['value' =>, 'label' =>, 'color' => ?, 'icon' => ?]
for select / multiselect |
| operators | array | by type | Override the operator list for this field |
| defaultOperator | string | by type | Operator applied when the filter is created |
| placeholder | string|null | null | Placeholder of the text value input |
| searchable | boolean | true | Show a search input above the options list |
Best Practices
Do
- Keep field keys stable — they are persisted in filter payloads
- Use
selectfor enums,multiselectfor tags - Bind with
wire:model.livewhen filtering Livewire queries - Provide colors for status-like options to improve scanability
Don't
- Don't put dozens of fields in one menu — split into groups or sideovers
- Don't use multiselect without handling array
valuesin your query - Don't change field keys between deploys if filters are stored in URLs or prefs