caliper uidocs

Filter toolbar

Free

A search field and filter menus with result counts, for narrowing a list.

Installs components/ui/filter-toolbar.tsxDepends on @radix-ui/react-dropdown-menu, @radix-ui/react-slot, lucide-reactMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/filter-toolbar.json
  • Atlas platform
  • Harbour redesign
  • Release notes
  • Wayfinder

When to use #

Use it for

  • Directories and tables with a few categorical filters.
  • You want an accessible count of matching results.

Skip it when

  • The list is short enough to scan.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/filter-toolbar.json

Adds its 8 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above. It is built from FilterToolbar and countOptions.

example.tsx
import { useMemo, useState } from "react"
import { FilterToolbar, type FilterValue } from "@/components/ui/filter-toolbar"

const projects = [
  { id: "atlas", name: "Atlas platform", owner: "Mara Chen", status: "Active", budget: 48000 },
  { id: "harbour", name: "Harbour redesign", owner: "Noor Haddad", status: "In review", budget: 21000 },
  { id: "notes", name: "Release notes", owner: "Ivo Petrov", status: "Active", budget: 6500 },
  { id: "wayfinder", name: "Wayfinder", owner: "Sana Okafor", status: "Paused", budget: 33000 },
];

export function ProjectFilters() {
  const [query, setQuery] = useState("");
  const [filters, setFilters] = useState<FilterValue>({});
  const shown = useMemo(
    () => projects.filter((project) => project.name.toLowerCase().includes(query.toLowerCase()) && (!filters.status?.length || filters.status.includes(project.status))),
    [query, filters],
  );
  return (
    <div className="grid w-full max-w-2xl gap-3">
      <FilterToolbar
        label="Projects"
        query={query}
        onQueryChange={setQuery}
        placeholder="Search projects"
        filters={[{ id: "status", label: "Status", options: ["Active", "In review", "Paused"].map((value) => ({ value, label: value })) }]}
        value={filters}
        onValueChange={setFilters}
        resultCount={shown.length}
        totalCount={projects.length}
        noun="projects"
      />
      <ul className="text-sm text-muted-foreground">{shown.map((project) => <li key={project.id}>{project.name}</li>)}</ul>
    </div>
  );
}

API reference #

FilterToolbar

component
PropTypeDefaultDescription
label *string—
query *string—
onQueryChange *(query: string) => void—
placeholderstring"Search"
filters *readonly FilterDefinition[]—
value *FilterValue—
onValueChange *(value: FilterValue) => void—
resultCountnumber—
totalCountnumber—
nounstring"results"
childrenReactNode—Extra actions aligned to the end, such as an export button.
classNamestring—

countOptions

component

No props of its own; it accepts the native props of its root element.

Accessibility #

It exposes search and status semantics, with aria-label, aria-hidden and aria-live kept in step with state. Changes that matter are announced through a live region.

Keyboard and focus handling comes from Radix dropdown-menu: arrow keys move through items, Enter and Space select, and Escape closes and returns focus.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.