21st

Filter Bar

Component 1

About

A dense list toolbar with facets and sort.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Filter Bar to your project and fits it to your theme.

Code

How Filter Bar is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client"

import * as React from "react"
import { CircleCheck, CircleDashed, CircleX, Flag } from "lucide-react"

import {
  FilterBar,
  applyFilterState,
  buildFacets,
  emptyFilterState,
  resolveSortRules,
  type FilterState,
} from "@/components/ui/filter-bar"

type Run = {
  id: string
  name: string
  suite: string
  model: string
  status: "passed" | "failed" | "running"
  score: number
  cases: number
  flagged: boolean
  updatedAt: string
  updatedLabel: string
}

const RUNS: Run[] = [
  {
    id: "r-9021",
    name: "Refusal calibration",
    suite: "Safety",
    model: "gpt-5",
    status: "failed",
    score: 71.4,
    cases: 480,
    flagged: true,
    updatedAt: "2026-07-27T09:12:00Z",
    updatedLabel: "12 min ago",
  },
  {
    id: "r-9020",
    name: "Long-context recall",
    suite: "Reasoning",
    model: "claude-opus-5",
    status: "passed",
    score: 94.8,
    cases: 320,
    flagged: false,
    updatedAt: "2026-07-27T08:40:00Z",
    updatedLabel: "44 min ago",
  },
  {
    id: "r-9018",
    name: "Tool-call arguments",
    suite: "Agents",
    model: "claude-opus-5",
    status: "passed",
    score: 91.2,
    cases: 210,
    flagged: false,
    updatedAt: "2026-07-27T07:05:00Z",
    updatedLabel: "2 hours ago",
  },
  {
    id: "r-9014",
    name: "SQL generation",
    suite: "Coding",
    model: "gemini-3-pro",
    status: "running",
    score: 0,
    cases: 640,
    flagged: false,
    updatedAt: "2026-07-27T06:30:00Z",
    updatedLabel: "3 hours ago",
  },
  {
    id: "r-9011",
    name: "Multi-hop retrieval",
    suite: "Reasoning",
    model: "gpt-5",
    status: "failed",
    score: 63.9,
    cases: 512,
    flagged: true,
    updatedAt: "2026-07-26T22:10:00Z",
    updatedLabel: "Yesterday",
  },
  {
    id: "r-9008",
    name: "Diff application",
    suite: "Coding",
    model: "claude-opus-5",
    status: "passed",
    score: 88.1,
    cases: 275,
    flagged: false,
    updatedAt: "2026-07-26T18:02:00Z",
    updatedLabel: "Yesterday",
  },
  {
    id: "r-9004",
    name: "Jailbreak resistance",
    suite: "Safety",
    model: "gemini-3-pro",
    status: "passed",
    score: 96.5,
    cases: 380,
    flagged: false,
    updatedAt: "2026-07-26T11:47:00Z",
    updatedLabel: "Yesterday",
  },
  {
    id: "r-8996",
    name: "Browser navigation",
    suite: "Agents",
    model: "gpt-5",
    status: "failed",
    score: 58.3,
    cases: 150,
    flagged: false,
    updatedAt: "2026-07-25T15:26:00Z",
    updatedLabel: "2 days ago",
  },
]

const STATUS_META = {
  passed: { label: "Passed", icon: CircleCheck, className: "text-emerald-600 dark:text-emerald-400" },
  failed: { label: "Failed", icon: CircleX, className: "text-red-600 dark:text-red-400" },
  running: { label: "Running", icon: CircleDashed, className: "text-muted-foreground" },
} as const

const SORT = {
  defaultProperty: "updatedAt",
  properties: [
    { value: "updatedAt", label: "Last run", defaultDirection: "desc" as const },
    { value: "score", label: "Score", defaultDirection: "desc" as const },
    { value: "cases", label: "Cases", defaultDirection: "desc" as const },
    { value: "name", label: "Name", defaultDirection: "asc" as const },
  ],
}

const titleCase = (value: string) =>
  value.charAt(0).toUpperCase() + value.slice(1)

export default function FilterBarDemo() {
  const [state, setState] = React.useState<FilterState>(emptyFilterState)

  // The bar sorts by "Last run" until you say otherwise; resolveSortRules hands
  // back that same default so the list matches the panel.
  const visible = applyFilterState(
    RUNS,
    { ...state, sort: resolveSortRules(state, SORT) },
    {
      facets: {
        model: (run) => run.model,
        suite: (run) => run.suite,
        status: (run) => run.status,
      },
      view: (run, view) =>
        view === "flagged" ? run.flagged : run.status === view,
      toggles: { flagged: (run) => run.flagged },
      search: (run) => `${run.name} ${run.suite} ${run.model}`,
      sort: {
        updatedAt: (run) => run.updatedAt,
        score: (run) => run.score,
        cases: (run) => run.cases,
        name: (run) => run.name,
      },
    },
  )

  return (
    // Fixed height, content pinned to the top: filtering changes how many rows
    // there are, and the frame must not resize under the toolbar while you use
    // it.
    <div className="mx-auto flex h-[760px] w-full max-w-3xl flex-col items-stretch overflow-hidden px-6 py-8">
      <div className="mb-5">
        <h2 className="text-[15px] font-medium text-foreground">Eval runs</h2>
        <p className="mt-0.5 text-[13px] text-muted-foreground">
          Every suite executed against the current model set.
        </p>
      </div>

      <FilterBar
        value={state}
        onValueChange={setState}
        searchPlaceholder="Search runs…"
        views={[
          { value: null, label: "All", count: RUNS.length },
          {
            value: "failed",
            label: "Failing",
            count: RUNS.filter((run) => run.status === "failed").length,
          },
          {
            value: "running",
            label: "Running",
            count: RUNS.filter((run) => run.status === "running").length,
          },
          {
            value: "flagged",
            label: "Flagged",
            count: RUNS.filter((run) => run.flagged).length,
            hint: "Runs someone marked for review",
          },
        ]}
        groups={[
          { key: "model", label: "Model", options: buildFacets(RUNS, "model") },
          { key: "suite", label: "Suite", options: buildFacets(RUNS, "suite") },
          {
            key: "status",
            label: "Status",
            options: buildFacets(RUNS, "status", titleCase),
          },
        ]}
        toggles={[
          { key: "flagged", label: "Flagged", hint: "Only runs marked for review" },
        ]}
        sort={SORT}
      />

      <div className="mt-4 min-h-0 flex-1 divide-y divide-border overflow-y-auto border-t border-border">
        {visible.map((run) => {
          const status = STATUS_META[run.status]
          const StatusIcon = status.icon
          return (
            <div
              key={run.id}
              className="flex items-center gap-3 py-2.5 transition-colors hover:bg-accent/40"
            >
              <StatusIcon className={`size-4 shrink-0 ${status.className}`} />
              <div className="min-w-0 flex-1">
                <div className="flex items-center gap-1.5">
                  <span className="truncate text-[13px] text-foreground">
                    {run.name}
                  </span>
                  {run.flagged ? (
                    <Flag className="size-3 shrink-0 text-amber-600 dark:text-amber-500" />
                  ) : null}
                </div>
                <div className="mt-0.5 flex items-center gap-1.5 text-[11.5px] text-muted-foreground">
                  <span>{run.suite}</span>
                  <span className="text-muted-foreground/50">·</span>
                  <span className="font-mono">{run.model}</span>
                  <span className="text-muted-foreground/50">·</span>
                  <span className="tabular-nums">{run.cases} cases</span>
                </div>
              </div>
              <span className="shrink-0 text-[13px] tabular-nums text-foreground">
                {run.status === "running" ? "—" : `${run.score.toFixed(1)}%`}
              </span>
              <span className="w-20 shrink-0 text-right text-[11.5px] tabular-nums text-muted-foreground">
                {run.updatedLabel}
              </span>
            </div>
          )
        })}
        {visible.length === 0 ? (
          <p className="py-10 text-center text-[13px] text-muted-foreground">
            No runs match these filters.
          </p>
        ) : null}
      </div>
    </div>
  )
}

Dependencies

More by 21st

View all
  • Spline Scene

    Spline Scene

    9.4K
  • Testimonials with Marquee

    Testimonials with Marquee

    2.3K
  • Gradient Button

    Gradient Button

    1.9K
  • Pixel Canvas

    Pixel Canvas

    899
  • Animated Blur Number

    Animated Blur Number

    830
  • Empty State

    Empty State

    774
  • Balloons

    Balloons

    755
  • Status Badge

    Status Badge

    642