PatternsCSS
Filter Bar
Layout for search, filters and actions above a list, with a reset button once anything is active.
Installation
npx shadcn@latest add https://ui-collections.vercel.app/r/glass/filter-bar.jsonAlso installs the theme, cn() and button. Packages: clsx, tailwind-merge.
Usage
filter-bar-demo.tsx
"use client";
import { useState } from "react";
import { DateRangePicker, type DateRange } from "@/registry/kit/ui/date-range-picker";
import { FilterBar } from "@/registry/kit/ui/filter-bar";
import { Button } from "@/registry/kit/ui/button";
import { MultiSelect } from "@/registry/kit/ui/multi-select";
import { SearchInput } from "@/registry/kit/ui/search-input";
const today = new Date(2026, 9, 26);
export default function FilterBarDemo() {
const [query, setQuery] = useState("");
const [plan, setPlan] = useState<string[]>([]);
const [range, setRange] = useState<DateRange | undefined>();
const [exporting, setExporting] = useState<"idle" | "busy" | "done">("idle");
const active = (query ? 1 : 0) + plan.length + (range ? 1 : 0);
return (
<div className="surface w-full max-w-4xl rounded-card p-4">
<FilterBar
search={<SearchInput label="Search invoices" value={query} onValueChange={setQuery} />}
filters={
<>
<MultiSelect
label="Plan"
value={plan}
onValueChange={setPlan}
options={[
{ value: "free", label: "Free" },
{ value: "pro", label: "Pro" },
{ value: "team", label: "Team" },
]}
/>
<DateRangePicker value={range} onValueChange={setRange} today={today} placeholder="Any date" />
</>
}
activeCount={active}
onReset={() => {
setQuery("");
setPlan([]);
setRange(undefined);
}}
end={
<Button
size="sm"
variant="solid"
shine={false}
loading={exporting === "busy"}
onClick={() => {
setExporting("busy");
setTimeout(() => setExporting("done"), 1200);
}}
>
{exporting === "done" ? "Exported 214 invoices" : "Export CSV"}
</Button>
}
/>
</div>
);
}API
searchReactNodeUsually a SearchInput; takes the flexible space.
filtersReactNodeMultiSelect, Combobox, DateRangePicker…
endReactNodeRight-aligned actions.
activeCount / onResetnumber / () => voidShows Reset when filters are active.
Source
components/ui/filter-bar.tsx
"use client";
import { cn } from "@/registry/kit/lib/utils";
import { Button } from "@/registry/kit/ui/button";
export interface FilterBarProps {
/** Usually a SearchInput; takes the flexible space. */
search?: React.ReactNode;
/** Filter controls such as MultiSelect, Combobox or DateRangePicker. */
filters?: React.ReactNode;
/** Right-aligned controls such as column visibility or view toggles. */
end?: React.ReactNode;
/** Number of active filters; shows the reset button when above zero. */
activeCount?: number;
onReset?: () => void;
className?: string;
}
/** Standard layout for search, filters and actions above a table or list. Wraps on small screens. */
export function FilterBar({ search, filters, end, activeCount = 0, onReset, className }: FilterBarProps) {
return (
<div role="search" className={cn("flex flex-wrap items-center gap-2", className)}>
{search && <div className="w-full sm:w-64 lg:w-72">{search}</div>}
<div className="flex flex-wrap items-center gap-2">
{filters}
{activeCount > 0 && onReset && (
<Button variant="ghost" size="sm" onClick={onReset} className="h-11 gap-1.5 px-3.5 text-sm font-normal text-muted">
<svg viewBox="0 0 24 24" aria-hidden className="size-3.5" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
<path d="M6 6l12 12M18 6 6 18" />
</svg>
Reset
<span className="sr-only">{activeCount} active filters</span>
</Button>
)}
</div>
{end && <div className="ml-auto flex flex-wrap items-center justify-end gap-2">{end}</div>}
</div>
);
}