Skip to content
Tier

Filter bar

Pro

Build filters one at a time: pick a field, a condition and a value (text, a number, one of the field's options, or a time window with presets like "Last 7 days"), then Add. The rules show as chips, each removable, with Clear all. The bar gives you FilterRule[] to query with; useFilterChips gives the same chips anywhere else.

Nothing is added until the person presses Add, and Add says what is missing until the rule is whole. The bar never filters anything itself: it hands the rules to you.

Markdown
Version 5.2.0· other versions

This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).

Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1 and 2.6.2: the page came later, or had another name then.

Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.

Preview

Loading the preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/filter-bar

Brings in: button, data, date-range-picker, filter-chips, input, label, locale, locked, number-input, popover, select, tokens, utils · npm: lucide-react@^1.67.0

Usage

TSX · The example above
"use client"

import * as React from "react"
import { FilterBar, type FilterField, type FilterRule } from "@/components/candor/filter-bar"

const fields: FilterField[] = [
  {
    id: "status",
    label: "Status",
    kind: "option",
    options: [
      { value: "running", label: "Running" },
      { value: "waiting", label: "Waiting on a person" },
      { value: "failed", label: "Failed" },
    ],
  },
  { id: "agent", label: "Agent", kind: "text" },
  { id: "cost", label: "Cost (USD)", kind: "number" },
  { id: "started", label: "Started", kind: "date" },
]

// The bar hands you the rules; your query applies them. Here they're only printed.
export default function Example() {
  const [rules, setRules] = React.useState<FilterRule[]>([{ id: "seed", field: "status", operator: "isNot", value: "failed" }])
  return (
    <div className="flex w-full flex-col gap-3">
      <FilterBar fields={fields} value={rules} onValueChange={setRules} />
      <pre className="overflow-x-auto rounded-md bg-muted p-3 text-xs">{JSON.stringify(rules.map(({ field, operator, value }) => ({ field, operator, value })), null, 2)}</pre>
    </div>
  )
}

<FilterBar>

PropTypeDefaultDescription
fieldsrequiredFilterField[]—What the person can filter on.
valueFilterRule[] | undefined—The rules, when you control them.
defaultValueFilterRule[] | undefined—The rules at first, when you don't control them.
onValueChange((rules: FilterRule[]) => void) | undefined—Called with every rule each time one is added or removed.
classNamestring | undefined—Classes for the bar.

Functions

useFilterChips(rules: FilterRule[], fields: FilterField[])

The chips for a set of rules, in the locale's words: "Status: not Closed", "Cost: more than 5". Pass them to FilterChips wherever the active filters show. A rule whose field is gone shows its field id and raw value.

Values

filterOperators

The conditions each kind offers, the first being the one picked when the field is.

Record<FilterFieldKind, FilterOperator[]>

Strings

The fixed words, in English by default. Change any of them under filterBar in <LocaleProvider strings={{ … }}>; the table is exported as filterBarStrings.

KeyEnglish
addAdd filter
fieldField
operatorCondition
valueValue
chooseFieldChoose a field
chooseValueChoose a value
applyAdd
needFieldChoose a field first.
needValueFill in a value first.
isis
isNotis not
containscontains
doesNotContaindoesn’t contain
equalsequals
notEqualsdoesn’t equal
greaterThanis more than
lessThanis less than
withinis within
chipIs{value}
chipIsNotnot {value}
chipContainscontains “{value}”
chipDoesNotContaindoesn’t contain “{value}”
chipEquals{value}
chipNotEqualsnot {value}
chipGreaterThanmore than {value}
chipLessThanless than {value}
chipWithin{value}

Types

FilterFieldKind
What a field holds, which decides its conditions and how its value is entered.
"text" | "number" | "option" | "date"
FilterOperator
How a rule compares the field with its value. Which ones a field offers follows its kind.
"is" | "isNot" | "contains" | "doesNotContain" | "equals" | "notEquals" | "greaterThan" | "lessThan" | "within"
FilterField
Something a person can filter on.
{ /** Kept on each rule as `field`. */ id: string; /** The field's name in your app's words: "Status", "Owner", "Started". */ label: string; /** What it holds: free text, a number, one of `options`, or a time. */ kind: FilterFieldKind; /** For an `option` field: the values to choose from. */ options?: { value: string; label: string; }[] | undefined; /** The conditions to offer, when not all of the kind's. */ operators?: FilterOperator[] | undefined; /** For a `date` field: the windows to offer. Defaults to DateRangePicker's. */ presets?: DateRangePreset[] | undefined; }
FilterRule
One filter the person added: a field, a condition and a value. Your query reads these.
{ /** The rule's own id, which is also its chip's. */ id: string; /** The `id` of the field it filters. */ field: string; /** How it compares. */ operator: FilterOperator; /** Text for a `text` field, an option's `value` for an `option` field, a number, or a time window. */ value: string | number | DateRangeValue; }