Filter toolbar

Keep collection filters close and easy to reset.

datafilter
Preview
Filter toolbar preview
TrialingDenis

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
TrialingDenis

2 filters applied.

Keyboard interactions

KeysAction
ArrowDown / ArrowUpOn the trigger, opens the menu at the first or last item; inside, moves between items.
Home / EndMoves to the first or last menu item.
ArrowRight / ArrowLeftDrills into a field's values, or goes back to fields.
LettersTypeahead jumps to the matching item.
EscapeCloses 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.