Data
QueryBuilder
A visual rule/condition builder — field, operator, value rows, groupable via AND/OR with nesting — the kind of filter UI Kendo/Syncfusion sell as a premium add-on.
QueryBuilderqueryToStringPreview
Usage
tsx
import { useState } from "react";
import { QueryBuilder, queryToString } from "@vesture/react";
import type { QueryField, QueryGroup } from "@vesture/react";
const fields: QueryField[] = [
{ key: "name", label: "Name", type: "text" },
{ key: "age", label: "Age", type: "number" },
{
key: "department",
label: "Department",
type: "select",
options: [
{ value: "engineering", label: "Engineering" },
{ value: "sales", label: "Sales" },
],
},
];
function Example() {
const [query, setQuery] = useState<QueryGroup>({
id: "root",
combinator: "and",
rules: [{ id: "r1", field: "name", operator: "contains", value: "Ada" }],
});
return (
<>
<QueryBuilder fields={fields} value={query} onChange={setQuery} />
<p>{queryToString(query)}</p>
</>
);
}Behavior
- Composes existing form components rather than reinventing input primitives — a rule row is a field Select, an operator Select, and a value input matching the field's type: Input for text, NumberInput for number, DatePicker for date, Select for a single select value.
- Operators are constrained per field type so invalid combinations can't be built — text gets contains/equals/starts with, number gets equals/greater than/less than/between, date gets equals/before/after/between, select gets equals/in. Changing a rule's field automatically resets its operator to a valid one for the new type if the old one no longer applies (kept as-is if it's still valid, e.g. "equals" works for both text and number).
- "between" on a number field renders two NumberInputs; "between" on a date field reuses DateRangePicker directly, since its {start, end} value shape already matches. "in" on a select field switches to Combobox in multi-select mode instead of a plain Select, for a friendly multi-value picker.
- Every group has its own AND/OR toggle, a list of rules and/or nested sub-groups, "+ Add rule" and "+ Add group" buttons, and a remove button per row/group. Nested groups render with a bordered, alternating-background sub-container so nesting depth stays visually legible rather than just structurally correct.
- maxDepth (default 3, root counts as depth 1) caps nesting — "+ Add group" disables once the limit is reached, so logic can't get arbitrarily/unmanageably deep.
- queryToString(group) serializes the whole tree into a human-readable string for a plain-English summary, e.g. "(status equals 'Active' AND age gt 25) OR department equals 'Engineering'" — also doubles as a quick sanity check that the data structure round-trips correctly.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| fields | QueryField[] | — | Required. { key, label, type: "text"|"number"|"date"|"select", options? } — options required when type is "select". |
| value | QueryGroup | — | Required. { id, combinator: "and"|"or", rules: (QueryRule | QueryGroup)[] } — recursive; groups can nest groups. |
| onChange | (group: QueryGroup) => void | — | Required. Fires with the updated tree on every add/remove/edit, at any nesting depth. |
| maxDepth | number | 3 | Maximum nesting depth for groups; root counts as depth 1. |