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
- 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-menuInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/add-filter-menuRun 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
Examples
Without AI
The input line
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
fields* | readonly AddFilterField[] | — | The fields that can filter, in the order offered. |
ai | AddFilterAi | — | The page's AI filter. Omit it where the AI module is off: no ✦ anywhere. |
initialQuery | string | "" | 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. |
labels | Partial<AddFilterMenuLabels> | English | The 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). |
open | boolean | — | Whether the popover is open (controlled). |
Data attributes and CSS variables on AddFilterMenu
| Attribute | Values |
|---|---|
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
| Prop | Type | Default | Description |
|---|---|---|---|
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. |
icon | React.ReactNode | — | A leading icon. |
keywords | readonly string[] | [] | Other words that find the field ("owner" for Assignee). |
AI
| Prop | Type | Default | Description |
|---|---|---|---|
onSubmit* | (text: string) => Promise<AiFilterResult> | AiFilterResult | — | Turn the request into filters. Reject (or throw) when the AI is unavailable or over its limit. |
placeholder | string | "Describe what you want, or pick a field" | The input's placeholder while AI is on. |
AiFilterInput
| Prop | Type | Default | Description |
|---|---|---|---|
onValueChange* | (value: string) => void | — | Called on every keystroke. |
value* | string | — | The text. |
ai | boolean | true | Lead 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). |
pending | boolean | false | A request is running: a spinner at the end of the row. |
Data attributes and CSS variables on AiFilterInput
| Attribute | Values |
|---|---|
data-ai | "" |
data-slot | "ai-filter-input" |
Accessibility
- The input is a
comboboxnamed "Filter" controlling alistboxof fields;aria-activedescendantfollows 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".
| Key | Action |
|---|---|
| ↑ ↓ | Move through the AI item and the fields |
| Enter | Run the AI request or open the highlighted field |
| Esc | Close the popover |
| Contract | States tested |
|---|---|
| Behaviour | default, open, pending, error |
| Accessibility | labeled, browser-accessibility-test |
| Visual | default, hover |