SearchFilterInput
Structured search control where each tag represents a field/operator/value filter.
Examples
Section titled “Examples”Default
Section titled “Default”() => { const [filters, setFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([]); const {config} = useSearchFilterInputConfig(basicFields);
return ( <SearchFilterInput config={config} filters={filters} onChange={next => setFilters(next)} /> );}With Searchable Enum
Section titled “With Searchable Enum”() => { const [filters, setFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([]); const {config} = useSearchFilterInputConfig([ { enumValues: DEPARTMENTS, key: 'department', label: 'Department', type: 'enum', }, ] as const);
return ( <SearchFilterInput config={config} filters={filters} onChange={next => setFilters(next)} /> );}With Pre Populated
Section titled “With Pre Populated”Clear Search
() => { const [filters, setFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([ { field: 'name', operator: 'contains', value: {type: 'string', value: 'John'}, }, { field: 'status', operator: 'is', value: {type: 'enum', value: 'active'}, }, ]); const {config} = useSearchFilterInputConfig(basicFields);
return ( <SearchFilterInput config={config} filters={filters} onChange={next => setFilters(next)} /> );}With Empty Operator
Section titled “With Empty Operator”Clear Search
() => { const [filters, setFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([ { field: 'name', operator: 'is_not_set', value: {type: 'empty'}, }, ]);
return ( <SearchFilterInput config={emptyOperatorConfig} filters={filters} onChange={next => setFilters(next)} /> );}All Field Types
Section titled “All Field Types”() => { const [filters, setFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([]); const {config} = useSearchFilterInputConfig([ {key: 'name', label: 'Name', type: 'string'}, {key: 'tags', label: 'Tags', type: 'string_list'}, {key: 'count', label: 'Count', type: 'number'}, {key: 'active', label: 'Active', type: 'boolean'}, {key: 'created', label: 'Created', type: 'date'}, {key: 'status', label: 'Status', type: 'enum', enumValues: STATUSES}, { key: 'priority', label: 'Priority', type: 'enum_list', enumValues: PRIORITIES, }, ] as const);
return ( <SearchFilterInput config={config} filters={filters} onChange={next => setFilters(next)} /> );}Clear Small
Clear Medium
Clear Large
() => { const [smFilters, setSmFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([ { field: 'name', operator: 'contains', value: {type: 'string', value: 'Alice'}, }, ]); const [mdFilters, setMdFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([ { field: 'name', operator: 'contains', value: {type: 'string', value: 'Alice'}, }, ]); const [lgFilters, setLgFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([ { field: 'name', operator: 'contains', value: {type: 'string', value: 'Alice'}, }, ]); const {config} = useSearchFilterInputConfig(basicFields);
return ( <div style={{display: 'flex', flexDirection: 'column', gap: 16}}> <SearchFilterInput config={config} filters={smFilters} label="Small" onChange={next => setSmFilters(next)} placeholder="Small..." size="sm" /> <SearchFilterInput config={config} filters={mdFilters} label="Medium" onChange={next => setMdFilters(next)} placeholder="Medium..." size="md" /> <SearchFilterInput config={config} filters={lgFilters} label="Large" onChange={next => setLgFilters(next)} placeholder="Large..." size="lg" /> </div> );}Disabled
Section titled “Disabled”Name containsJohn
() => { const {config} = useSearchFilterInputConfig(basicFields);
return ( <SearchFilterInput config={config} filters={[ { field: 'name', operator: 'contains', value: {type: 'string', value: 'John'}, }, ]} isDisabled onChange={() => {}} /> );}Read Only
Section titled “Read Only”Name containsJohnStatus isActive
() => { const {config} = useSearchFilterInputConfig(basicFields);
return ( <SearchFilterInput config={config} filters={[ { field: 'name', operator: 'contains', value: {type: 'string', value: 'John'}, }, { field: 'status', operator: 'is', value: {type: 'enum', value: 'active'}, }, ]} isReadOnly onChange={() => {}} /> );}With Result Count
Section titled “With Result Count”42 results
() => { const [filters, setFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([]); const {config} = useSearchFilterInputConfig(basicFields);
return ( <SearchFilterInput config={config} filters={filters} onChange={next => setFilters(next)} resultCount={42} /> );}With Visible Label
Section titled “With Visible Label”() => { const [filters, setFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([]); const {config} = useSearchFilterInputConfig(basicFields);
return ( <SearchFilterInput config={config} filters={filters} isLabelHidden={false} label="Filter users" onChange={next => setFilters(next)} /> );}With Error Status
Section titled “With Error Status”At least one filter is required
() => { const [filters, setFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([]); const {config} = useSearchFilterInputConfig(basicFields);
return ( <SearchFilterInput config={config} filters={filters} onChange={next => setFilters(next)} status={{type: 'error', message: 'At least one filter is required'}} /> );}With Many Filters
Section titled “With Many Filters”Clear Search
() => { const [filters, setFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([ { field: 'name', operator: 'contains', value: {type: 'string', value: 'Alice'}, }, { field: 'name', operator: 'contains', value: {type: 'string', value: 'Bob'}, }, { field: 'status', operator: 'is', value: {type: 'enum', value: 'active'}, }, { field: 'status', operator: 'is', value: {type: 'enum', value: 'pending'}, }, { field: 'age', operator: 'equals', value: {type: 'integer', value: 30}, }, { field: 'name', operator: 'contains', value: {type: 'string', value: 'Charlie'}, }, ]); const {config} = useSearchFilterInputConfig(basicFields);
return ( <div style={{maxWidth: 600}}> <SearchFilterInput config={config} filters={filters} onChange={next => setFilters(next)} tagOverflowBehavior="unfocusedInline" /> </div> );}With Custom Components
Section titled “With Custom Components”Clear Search
() => { const [filters, setFilters] = useState< ReadonlyArray<SearchFilterInputFilter> >([ { field: 'status', operator: 'is', value: {type: 'enum', value: 'active'}, }, ]); const {config} = useSearchFilterInputConfig(basicFields);
// Override the tag and editor for the `enum` value type only; all other // field types keep the default rendering. Click the pill to open the custom // editor. const components: SearchFilterInputComponents = { enum: {Editor: StatusPickerEditor, Tag: StatusBadgeTag}, };
return ( <SearchFilterInput components={components} config={config} filters={filters} onChange={next => setFilters(next)} /> );}With Timezone
Section titled “With Timezone”Clear America/Los_Angeles
Clear America/New_York
Clear Europe/London
Clear Asia/Tokyo
() => { const {config} = useSearchFilterInputConfig([ {key: 'created', label: 'Created', type: 'date'}, ] as const);
// A single fixed instant (2026-01-15 12:00 UTC). Each input below renders // the same filter with a different timezoneID, so the tag's formatted // date/time differs per zone. const unixSeconds = Math.floor( Temporal.Instant.from('2026-01-15T12:00:00Z').epochMilliseconds / 1000, ); const filters: ReadonlyArray<SearchFilterInputFilter> = [ { field: 'created', operator: 'after', value: {type: 'date_absolute', unixSeconds}, }, ]; const timezones = [ 'America/Los_Angeles', 'America/New_York', 'Europe/London', 'Asia/Tokyo', ];
return ( <div style={{display: 'flex', flexDirection: 'column', gap: 16}}> {timezones.map(timezone => ( <SearchFilterInput config={config} filters={filters} isLabelHidden={false} key={timezone} label={timezone} onChange={() => {}} timezoneID={timezone} /> ))} </div> );}SearchFilterInput
Section titled “SearchFilterInput”Structured search control where each tag represents a field/operator/value filter.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Additional CSS class names applied to the input wrapper. |
components | SearchFilterInputComponents | — | Per-value-type component overrides. |
config* | SearchFilterInputConfig | — | SearchFilterInput field/operator configuration. |
data-testid | string | — | Test ID applied to the input wrapper. |
endContent | ReactNode | — | Content displayed at the end of the input row. |
filters* | ReadonlyArray<SearchFilterInputFilter> | — | Current filters. |
handleRef | Ref<SearchFilterInputHandle> | — | Imperative focus/blur handle. |
hasAutoFocus | boolean | false | Whether to focus the input on mount. |
hasClear | boolean | true | Whether to show a clear button. |
isDisabled | boolean | false | Whether the input is disabled. |
isLabelHidden | boolean | true | Whether to visually hide the label. |
isReadOnly | boolean | false | Whether filters are read-only. |
label | string | 'Search' | Accessible label. |
maxOperatorMenuItems | number | — | Maximum number of items shown in the operator dropdown menu. When set, only the first N operators are displayed. |
maxTagLength | number | 40 | Maximum displayed tag value length. |
onBlur | (event: FocusEvent<HTMLDivElement>) => void | — | Called when focus leaves the control. |
onChange* | ( filters: ReadonlyArray<SearchFilterInputFilter>, changeType: SearchFilterInputChangeType, index: number, ) => void | — | Called when filters change. |
onFocus | (event: FocusEvent<HTMLDivElement>) => void | — | Called when focus enters the control. |
placeholder | string | 'Search...' | Placeholder text. |
popoverSaveButtonLabel | string | 'Apply' | Save button label in the edit popover. |
ref | Ref<HTMLDivElement> | — | Ref forwarded to the root element. |
resultCount | number | string | — | Result count displayed at end of row. |
size | "sm" | "md" | "lg" | 'md' | Visual size. |
startIcon | IconComponent | — | Icon shown before the input. |
status | InputStatus | — | Validation status displayed below the input. |
style | CSSProperties | — | Inline styles applied to the input wrapper. |
tagOverflowBehavior | "none" | "unfocusedInline" | "unfocusedLayer" | 'none' | Controls how tags overflow when the container is too narrow. - 'none': Tags wrap to multiple lines (default). - 'unfocusedInline': Single line with "+ N more" when unfocused; expands inline on focus. - 'unfocusedLayer': Single line with "+ N more" when unfocused; expands as overlay on focus. |
timezoneID | string | — | Timezone ID for date formatting. |
SearchFilterInputEditPopover
Section titled “SearchFilterInputEditPopover”| Prop | Type | Default | Description |
|---|---|---|---|
config* | InternalSearchFilterInputConfig | — | Internal config lookup helpers. |
filter* | PartialFilter | — | Partial filter being created or edited. |
isReadOnly | boolean | false | Whether controls are read-only. |
maxOperatorMenuItems | number | — | Maximum number of items shown in the operator dropdown menu. |
mode* | 'create' | 'edit' | — | Editor mode. |
onCancel* | () => void | — | Called when editing is cancelled. |
onSave* | (filter: SearchFilterInputFilter | null) => void | — | Called with a completed filter, or null to delete. |
saveButtonLabel | string | 'Apply' | Save button label. |
SearchFilterInputTag
Section titled “SearchFilterInputTag”Default tag renderer for a SearchFilterInput filter.
| Prop | Type | Default | Description |
|---|---|---|---|
config* | SearchFilterInputConfig | — | |
field* | SearchFilterInputField | — | |
filter* | SearchFilterInputFilter | — | |
isDisabled | boolean | — | |
maxLength* | number | — | |
onClick | () => void | — | |
onRemove | () => void | — | |
operator* | SearchFilterInputOperator | — |