Skip to content
MIZU.

SIG—09 Signature system

QueryBuilder

Conditions that read as a sentence: typed operators per field, nested any/all groups, a plain-language reading and an evaluator for local filtering.

View .md

Live — this one is real

Scenario

Illustrative accounts. The table below is filtered live with matchesQuery; the reading comes from describeQuery.

Conditions: match all
Match all or any conditions
  1. seats
  2. Group: match any
    Match all or any conditions in this group
    1. days
    2. Region3 selected
      Region

Reads asPlan is any of Pro, Team, and Seats ≥ 10 seats, and (Signed up is in the last 90 days, or Region is any of Germany, Norway, Japan)

Matching accounts — Plan is any of Pro, Team, and Seats ≥ 10 seats, and (Signed up is in the last 90 days, or Region is any of Germany, Norway, Japan)
Region
Cedar Studiopro17India2026-09-22
Bright Labspro16Japan2026-02-11
Harbor Healthpro10Japan2026-08-13
Harbor Foodspro14Canada2026-08-18
Kite Studioteam12Canada2026-08-31
Signal Studiopro11Norway2026-06-20

Usage

tsx
"use client";

import { useState } from "react";
import { QueryBuilder, type QueryGroup } from "mizu-ui";

export function Example() {
  const [query, setQuery] = useState<QueryGroup>({
    id: "root",
    combinator: "and",
    rules: [{ id: "a", field: "plan", operator: "is", value: "pro" }],
  });
  return (
    <QueryBuilder
      label="Audience"
      value={query}
      onValueChange={setQuery}
      fields={[
        {
          id: "plan",
          label: "Plan",
          type: "select",
          options: [
            { value: "free", label: "Free" },
            { value: "pro", label: "Pro" },
          ],
        },
        { id: "seats", label: "Seats", type: "number" },
        { id: "joined", label: "Joined", type: "date" },
      ]}
    />
  );
}

It starts with "use client" because it keeps state or passes functions to components, so it pastes into a Next.js page as it is. Other React setups ignore the directive.

This code, running — no configuration beyond the data

Conditions: match all
Match all or any conditions

Reads asPlan is Pro

Source & setup

Add this component to your project with the shadcn CLI. The command copies its source into your configured UI directory, with base.css, Mizu’s tokens, and the stylesheets that hold its rules. Each rule lives in one stylesheet, so components you add together never repeat CSS. In a project without a components.json, run npx shadcn@latest init 0xuser64bit/mizu/preset first: plain init installs shadcn’s theme into your global CSS. Then load the fonts.

sh
npx shadcn@latest add 0xuser64bit/mizu/query-builder

Every component imports from its own path, its name in kebab case. To paste the example, replace its mizu-ui import with:

tsx
import { QueryBuilder, type QueryGroup } from "@/components/ui/mizu/query-builder";

For npm imports, import mizu-ui/styles.css once. Fonts are optional via mizu-ui/fonts.css. Override --mizu-* tokens or use className for local styling. Requires React and Motion. A Client Component: its source starts with “use client”.

Inspect signature/QueryBuilder.tsx

Source is included in the npm package under src/signature/QueryBuilder.tsx.

Open full source ↗

Props

PropTypeDefaultDescription
label / fieldsstring / QueryField[]—Region name; fields have id, label, type (text, number, date, select, boolean), options and unit.
value / defaultValue / onValueChangeQueryGroup—The query: a combinator and rules or nested groups.
maxDepthnumber3Group nesting limit.
footerReactNode—Beside the reading, e.g. a live match count.
describeQuery(query, fields)function—The plain-language reading, for saved segments.
matchesQuery(query, record, fields, now?)function—Evaluate a record locally; incomplete rules are ignored.

Accessibility

Each group is a fieldset; the all/any choice is a real radio group. Every condition is a named group of native selects and inputs with specific labels, and an incomplete condition is described in words. Additions and removals are announced, and a new condition receives focus.

Motion

New conditions unfold from the left, removed ones close, and the and/or joins roll over like a counter when a group's logic changes. Reduced motion shows each change immediately.

Works alongside