Vesture

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.

QueryBuilderqueryToString

Preview

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

PropTypeDefaultDescription
fieldsQueryField[]Required. { key, label, type: "text"|"number"|"date"|"select", options? } — options required when type is "select".
valueQueryGroupRequired. { id, combinator: "and"|"or", rules: (QueryRule | QueryGroup)[] } — recursive; groups can nest groups.
onChange(group: QueryGroup) => voidRequired. Fires with the updated tree on every add/remove/edit, at any nesting depth.
maxDepthnumber3Maximum nesting depth for groups; root counts as depth 1.