PopoverFilter
freePopoverFilter combines a search input and checkbox list behind a popover trigger, for filtering a list or table by more than one value.
@floating-ui/react
Preview
Dark
import { useState } from 'react';
import PopoverFilter from '@/components/composites/PopoverFilter';
const priorityData = [
{ label: 'Urgent', value: 'urgent' },
{ label: 'High', value: 'high' },
{ label: 'Medium', value: 'medium' },
{ label: 'Low', value: 'low' },
{ label: 'Backlog', value: 'backlog' },
];
export default function UsageDemo() {
const [selected, setSelected] = useState<string[]>([]);
const title =
selected.length === 0
? 'Priority'
: selected.length === 1
? priorityData.find((item) => item.value === selected[0])?.label
: `${selected.length} selected`;
return (
<div className="flex justify-center">
<PopoverFilter
data={priorityData}
title={title}
value={selected}
onChange={(data) => setSelected(data.map((item) => item.value))}
/>
</div>
);
}Installation
Add this component with the NateUI CLI.
npx nateui@latest add PopoverFilterExamples
Custom Trigger
Preview
Dark
import { useState } from 'react';
import { PiFunnel } from 'react-icons/pi'
import Button from '@/components/ui/Button';
import PopoverFilter from '@/components/composites/PopoverFilter';
const teamData = [
{ label: 'Design', value: 'design' },
{ label: 'Engineering', value: 'engineering' },
{ label: 'Product', value: 'product' },
{ label: 'Marketing', value: 'marketing' },
{ label: 'Sales', value: 'sales' },
{ label: 'Support', value: 'support' },
];
export default function CustomTriggerDemo() {
const [selected, setSelected] = useState<string[]>(['engineering']);
return (
<div className="flex justify-center">
<PopoverFilter
data={teamData}
value={selected}
onChange={(data) => setSelected(data.map((item) => item.value))}
renderTrigger={
<Button icon={<PiFunnel />}>
Team{selected.length > 0 && ` (${selected.length})`}
</Button>
}
/>
</div>
);
}Hide Reset
Preview
Dark
import PopoverFilter from '@/components/composites/PopoverFilter';
const regionData = [
{ label: 'North America', value: 'na' },
{ label: 'Europe', value: 'eu' },
{ label: 'Asia Pacific', value: 'apac' },
{ label: 'Latin America', value: 'latam' },
];
export default function ShowResetDemo() {
return (
<div className="flex justify-center">
<PopoverFilter
data={regionData}
title="Region"
showReset={false}
/>
</div>
);
}Placement
Preview
Dark
import PopoverFilter from '@/components/composites/PopoverFilter';
const typeData = [
{ label: 'Bug', value: 'bug' },
{ label: 'Feature', value: 'feature' },
{ label: 'Task', value: 'task' },
{ label: 'Chore', value: 'chore' },
];
export default function PlacementDemo() {
return (
<div className="flex flex-wrap justify-center gap-8">
<PopoverFilter
data={typeData}
title="Bottom Start"
placement="bottom-start"
/>
<PopoverFilter
data={typeData}
title="Bottom End"
placement="bottom-end"
/>
</div>
);
}API
PopoverFilter wraps Popover with a searchable checkbox list. onChange receives the matching subset of data, not the raw selected values — pair it with .map((item) => item.value) to recover a plain string array (see Usage). When renderTrigger is set it fully replaces the default trigger button, so title has no visible effect. A search term with no matches renders a plain "No results found" message in place of the checkbox list.
| Prop | Description | Type | Default |
|---|---|---|---|
data | Filter options rendered as checkboxes. | PopoverFilterOption[] | — |
value | Controlled selected values. Re-syncs the internal selection whenever it changes. | string[] | — |
onChange | Called with the subset of data matching the new selection — not the raw selected values. | (data: PopoverFilterOption[]) => void | — |
title | Label for the default trigger button. Ignored when renderTrigger is set. | string | ReactNode | 'Filter' |
renderTrigger | Custom trigger element, replacing the default button entirely. | ReactNode | — |
placement | Side the popover opens from, relative to the trigger. | 'bottom-start' | 'bottom-end' | 'bottom-start' |
inputPlaceholder | Placeholder text for the search input. | string | 'Search...' |
showReset | Show a "Clear filters" button once at least one option is selected. | boolean | true |
width | Width of the popover panel, in pixels. | number | 220 |
Types
type PopoverFilterOption = {
label: string;
value: string;
}