Filter toolbar
Keep collection filters close and easy to reset.
datafilter
Preview
Filter toolbar preview
Live · keyboard ready
Chips narrow the rows rendered below the toolbar.
When to use
- Faceted filtering above tables and lists, with removable chips.
- Adding filters through a two step field then value menu.
- Views that pair with sortable-data-table and pagination for a full list page.
When not to use
- Use chip-group for a fixed set of toggles.
- Use search-field for free text search.
- Use multi-select when one field takes several values in a form.
shadcn CLI
Also installs (1)
- manicat-foundation
Manual install(+)
registry.jsonjson
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "filter-toolbar",
"type": "registry:ui",
"title": "Filter toolbar",
"description": "Keep collection filters close and easy to reset.",
"categories": [
"data"
],
"dependencies": [
"lucide-react",
"motion"
],
"registryDependencies": [
"https://manicat.viraza.net/r/manicat-foundation.json"
],
"files": [
{
"path": "registry/components/filter-toolbar/filter-toolbar.tsx",
"type": "registry:component",
"target": "@components/manicat/filter-toolbar/filter-toolbar.tsx"
},
{
"path": "registry/components/filter-toolbar/filter-toolbar.module.css",
"type": "registry:component",
"target": "@components/manicat/filter-toolbar/filter-toolbar.module.css"
}
],
"docs": "Docs and live preview: https://uiarc.dev/components/filter-toolbar",
"meta": {
"tier": "free",
"kind": "component",
"docs": "https://manicat.viraza.net/components/filter-toolbar",
"markdown": "https://uiarc.dev/components/filter-toolbar/markdown",
"tags": [
"data",
"filter"
]
}
}Usage
Install the component, then copy this into your app. Change the content and the props to fit your screen.
example.tsxtsx
"use client";
import { useState } from "react";
import { FilterToolbar } from "@/components/manicat/filter-toolbar/filter-toolbar";
import type { FilterChip, FilterField } from "@/components/manicat/filter-toolbar/filter-toolbar";
const filterFields: FilterField[] = [
{ id: "status", label: "Status", options: ["Trialing", "Active", "Paused", "Canceled"] },
{ id: "assignee", label: "Assignee", options: ["Elia", "Mara", "Denis", "Ravi", "Sofia"] },
{
id: "priority",
label: "Priority",
options: [{ value: "low" }, { value: "medium" }, { value: "high" }, { value: "urgent", hint: 2 }],
},
];
export function FilterToolbarExample() {
const [filters, setFilters] = useState<FilterChip[]>([
{ id: "status", label: "Trialing" },
{ id: "assignee", label: "Denis" },
]);
return (
<div className="flex w-full max-w-md flex-col gap-3">
<FilterToolbar
filters={filters}
onRemove={(id) => setFilters((current) => current.filter((filter) => filter.id !== id))}
onClearAll={() => setFilters([])}
addFilter={{ fields: filterFields, onAdd: (filter) => setFilters((current) => [...current, filter]) }}
>
<input
className="min-w-40 rounded-[var(--radius-control)] border border-(color:--border) bg-(--background) px-3 py-1.5 text-sm outline-none transition focus:border-(color:--foreground)"
placeholder="Search the team"
/>
</FilterToolbar>
<p className="text-xs text-(color:--text-muted)">Chips narrow the rows rendered below the toolbar.</p>
</div>
);
}
Examples
Real screens rather than isolated props. Switch to Code to copy a whole example into your project.
Customers filters Chips for the active filters, an add-filter menu and your own search field inside the toolbar.
Customers filters
Chips for the active filters, an add-filter menu and your own search field inside the toolbar. preview
Live · keyboard ready
2 filters applied.
customers-filters-chips-for-the-active-filters-an-add-filter-menu-and-your-own-search-field-inside-the-toolbar-.tsxtsx
Keyboard interactions
| Keys | Action |
|---|---|
| ArrowDown / ArrowUp | On the trigger, opens the menu at the first or last item; inside, moves between items. |
| Home / End | Moves to the first or last menu item. |
| ArrowRight / ArrowLeft | Drills into a field's values, or goes back to fields. |
| Letters | Typeahead jumps to the matching item. |
| Escape | Closes the menu and returns focus to the trigger. |
Accessibility
- The toolbar is role="group"; the menu panel is a labelled role="dialog" holding a role="menu" of checkable items.
- Remove buttons are labelled like "Remove Status: Open", and removing a chip moves focus to its neighbor or the Add filter trigger.
- Additions and removals are announced through a role="status" region.
Motion
- Chips open and close their slot on a spring so neighbors glide; values morph in place.
- The Add filter trigger morphs into the menu surface and steps slide between fields and values.
- Reduced motion replaces the morphs and slides with instant changes and short fades.
Responsive behavior
- Below 520px the toolbar stacks into a column with actions aligned to the end.
- The menu panel is min(16rem, 100vw minus a gutter) wide, flips its edge when there is no room, and its list caps at min(20rem, 55vh).
- Hover highlights apply only on hover-capable fine pointers.
Performance
- Several ResizeObservers measure chips and the menu for morphs; menu options are not virtualized, so keep value lists short.
Notes for AI
- Use above tables and lists for faceted filtering. Use chip-group for a fixed set of toggles and search-field for free text.
- Keep one chip per field: onAdd receives a chip keyed by field id, so replace any existing chip with that id.
- Pair with sortable-data-table and pagination for a full list view.