Skip to content
Component installs need the registry setup
VegaStack Design

Add Filter Menu

+ Filter — one input that narrows the fields or sends a plain-language request to the page's AI filter; fields open their pickers in place.

Status
stable
Since
0.25.100
Accessibility pattern
combobox of fields; polite status; sheet on a phone

Last updated

Install

Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Add Filter Menu before installation. Retain the digest printed by this command.

pnpm exec vegastack-design verify add-filter-menu

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/add-filter-menu

Run the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.

The same command installs the registry items it composes: @vegastack/button, @vegastack/list-toolbar, @vegastack/panel-search, @vegastack/spinner.

Usage

import { AddFilterMenu } from "@/components/ui/add-filter-menu";

<AddFilterMenu
  fields={[
    {
      key: "priority",
      label: "Priority",
      icon: <Flag />,
      editor: (done) => <PriorityPicker onPick={done} />,
    },
    {
      key: "assignee",
      label: "Assignee",
      icon: <UserRound />,
      keywords: ["owner"],
    },
  ]}
  onPick={addFilter}
  ai={
    aiAvailable
      ? { onSubmit: (text) => applyAiFilter(listKey, text) }
      : undefined
  }
  onAdvanced={openFilterBuilder}
/>;

The popover opens on one input. Typing narrows the field list (by label and keywords); ↑ ↓ pick a field and Enter opens it. A field with an editor drills into it in the same popover, with a ‹ back header — never a second surface; a field without one calls onPick and closes (the host adds the chip, its editor open).

With ai, the input's placeholder becomes "Describe what you want, or pick a field" and leads with ✦, and the first item is "✦ Filter with AI: “…”". Enter on it — or Enter with no field matching exactly — calls ai.onSubmit(text). While it runs the input shows a spinner and the item reads "Working…". On success (added > 0) the popover closes and the page shows the chips and the FilterRow note; with nothing added, or when onSubmit rejects (a model error, a daily limit), the popover says so and the field list stays usable. Without ai there is no ✦ anywhere and nothing else changes, so the feature hides cleanly. The design system holds no AI: onSubmit is the page's own.

onAdvanced adds "Advanced filter…" at the foot, for a condition builder dialog. On a phone the popover is a bottom sheet with the input at its top.

AiFilterInput is the input line on its own — a borderless field on the surface's hairline row, ✦ or a magnifier, a spinner while pending — for a phone's filter sheet.

Anatomy

AddFilterMenu — data-slot="add-filter-menu-advanced" | "add-filter-menu-ai" | "add-filter-menu-back" | "add-filter-menu-body" | "add-filter-menu-content" | "add-filter-menu-editor" | "add-filter-menu-field" | "add-filter-menu-list" | "add-filter-menu-message" | "add-filter-menu-trigger"
AiFilterInput — data-slot="ai-filter-input"

Examples

Without AI

The input line

API Reference

PropTypeDefaultDescription
fields*readonly AddFilterField[]—The fields that can filter, in the order offered.
aiAddFilterAi—The page's AI filter. Omit it where the AI module is off: no ✦ anywhere.
initialQuerystring""The text the input opens with — the last AI prompt when "Edit filters" reopens the menu from a list with no results. Each opening starts from it.
labelsPartial<AddFilterMenuLabels>EnglishThe component's words.
onAdvanced(() => void)—"Advanced filter…" at the foot (a condition builder dialog).
onOpenChange((open: boolean) => void)—Called when it opens or closes.
onPick((key: string) => void)—Called when a field without an editor is picked: add its chip (with its editor open).
openboolean—Whether the popover is open (controlled).

Data attributes and CSS variables on AddFilterMenu

AttributeValues
data-highlighted""
data-slot"add-filter-menu-advanced" | "add-filter-menu-ai" | "add-filter-menu-back" | "add-filter-menu-body" | "add-filter-menu-content" | "add-filter-menu-editor" | "add-filter-menu-field" | "add-filter-menu-list" | "add-filter-menu-message" | "add-filter-menu-trigger"

Field

PropTypeDefaultDescription
key*string—Stable key.
label*string—The field's name ("Priority").
editor((done: () => void) => React.ReactNode) | React.ReactNode—The value picker that opens in place when the field is picked. A function gets done, which closes the popover. Omitted, picking the field calls onPick and closes.
iconReact.ReactNode—A leading icon.
keywordsreadonly string[][]Other words that find the field ("owner" for Assignee).

AI

PropTypeDefaultDescription
onSubmit*(text: string) => Promise<AiFilterResult> | AiFilterResult—Turn the request into filters. Reject (or throw) when the AI is unavailable or over its limit.
placeholderstring"Describe what you want, or pick a field"The input's placeholder while AI is on.

AiFilterInput

PropTypeDefaultDescription
onValueChange*(value: string) => void—Called on every keystroke.
value*string—The text.
aibooleantrueLead with ✦: the field also takes plain-language requests.
onSubmit((text: string) => void)—Called with the text on Enter (when the host does not handle Enter itself).
pendingbooleanfalseA request is running: a spinner at the end of the row.

Data attributes and CSS variables on AiFilterInput

AttributeValues
data-ai""
data-slot"ai-filter-input"

Accessibility

  • The input is a combobox named "Filter" controlling a listbox of fields; aria-activedescendant follows the arrow keys, so focus stays in the input.
  • The AI item and each field are options; a field that drills in shows a chevron.
  • Results and errors appear in a polite status; the drill-in's back button is named "Back".
KeyAction
↑ ↓Move through the AI item and the fields
EnterRun the AI request or open the highlighted field
EscClose the popover
ContractStates tested
Behaviourdefault, open, pending, error
Accessibilitylabeled, browser-accessibility-test
Visualdefault, hover

Do / Don't

Do
Pass ai only where the AI module is on, and return what the AI added and the words it could not match.
Don't
Show a disabled ✦ when AI is off, or apply AI results as anything but ordinary chips.

On this page