{"id":43309979,"url":"https://github.com/nandhakumare/react-querybuilder-lite","last_synced_at":"2026-02-01T21:01:12.335Z","repository":{"id":332148032,"uuid":"1126293330","full_name":"NandhakumarE/react-querybuilder-lite","owner":"NandhakumarE","description":"Headless React query builder with compound components, render props, TypeScript \u0026 drag-and-drop. Works with any design system.","archived":false,"fork":false,"pushed_at":"2026-01-23T17:50:58.000Z","size":420,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-24T07:51:26.346Z","etag":null,"topics":["compound-components","drag-and-drop","filter-builder","headless-ui","query-builder","react","react-hooks","render-props","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/NandhakumarE.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-01T15:40:16.000Z","updated_at":"2026-01-20T17:50:48.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/NandhakumarE/react-querybuilder-lite","commit_stats":null,"previous_names":["nandhakumare/react-visual-querybuilder","nandhakumare/react-querybuilder-lite"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/NandhakumarE/react-querybuilder-lite","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NandhakumarE%2Freact-querybuilder-lite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NandhakumarE%2Freact-querybuilder-lite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NandhakumarE%2Freact-querybuilder-lite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NandhakumarE%2Freact-querybuilder-lite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NandhakumarE","download_url":"https://codeload.github.com/NandhakumarE/react-querybuilder-lite/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NandhakumarE%2Freact-querybuilder-lite/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28990682,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T20:57:35.821Z","status":"ssl_error","status_checked_at":"2026-02-01T20:57:29.580Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compound-components","drag-and-drop","filter-builder","headless-ui","query-builder","react","react-hooks","render-props","typescript"],"created_at":"2026-02-01T21:01:11.574Z","updated_at":"2026-02-01T21:01:12.306Z","avatar_url":"https://github.com/NandhakumarE.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-querybuilder-lite\n\n[![npm version](https://img.shields.io/npm/v/react-querybuilder-lite.svg)](https://www.npmjs.com/package/react-querybuilder-lite)\n![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)\n![React](https://img.shields.io/badge/React-16.8%2B-61dafb)\n[![License](https://img.shields.io/npm/l/react-querybuilder-lite)](https://github.com/NandhakumarE/react-visual-querybuilder/blob/main/LICENSE)\n[![Storybook](https://img.shields.io/badge/Storybook-Live%20Demo-ff4785)](https://nandhakumare.github.io/react-querybuilder-lite/)\n[![CodeSandbox](https://img.shields.io/badge/CodeSandbox-Try%20It-000?logo=codesandbox)](https://codesandbox.io/p/sandbox/hmkk77)\n\n\nA lightweight, headless React query builder with drag-and-drop support. Build complex filter UIs with any design system — zero styling opinions.\n\n## Why This Library?\n\nMost query builders ship with opinionated styles or are tightly coupled to specific UI libraries. This library provides:\n\n- **Complete UI freedom** — Use MUI, Chakra, Ant Design, Tailwind, or vanilla HTML\n- **Inversion of control** — You own the markup, we handle the logic\n- **Type safety** — Full TypeScript inference for queries, operators, and fields\n- **Lightweight** — ~18KB minified + gzipped, including drag-and-drop support\n\n## Features\n\n| Feature | Description |\n|---------|-------------|\n| Headless | No styles, no markup — bring your own components |\n| Compound Components | Clean `\u003cQueryBuilder.Builder\u003e` composition API |\n| Render Props | Full control via `renderRule` and `renderGroup` |\n| Drag \u0026 Drop | Optional reordering via dnd-kit integration |\n| Immutable Updates | Predictable state with structural sharing |\n| Type Inference | Operators auto-filter based on field type |\n| Nested Groups | Recursive AND/OR groups with `maxDepth` control |\n| Lock Protection | Prevent modification of locked rules/groups |\n| Slot Actions | Pre-wired handlers for add, remove, clone, lock |\n\n## Installation\n\n```bash\nnpm install react-querybuilder-lite\n```\n\n```bash\nyarn add react-querybuilder-lite\n```\n\n```bash\npnpm add react-querybuilder-lite\n```\n\n**Peer Dependencies:** React 16.8+\n\n## Quick Start\n\n```tsx\nimport { useState } from 'react';\nimport { QueryBuilder, type Query } from 'react-querybuilder-lite';\n\nconst fields = [\n  { label: 'Name', value: 'name', type: 'string' },\n  { label: 'Age', value: 'age', type: 'number' },\n];\n\nconst initialQuery: Query = {\n  id: 'root',\n  combinator: 'and',\n  rules: [],\n};\n\nfunction App() {\n  const [query, setQuery] = useState\u003cQuery\u003e(initialQuery);\n\n  return (\n    \u003cQueryBuilder value={query} onChange={setQuery} maxDepth={3}\u003e\n      \u003cQueryBuilder.Builder\n        fields={fields}\n        renderRule={({ rule, fields, operators, onChange, slots }) =\u003e (\n          \u003cdiv className=\"rule\"\u003e\n            \u003cselect\n              value={rule.field}\n              onChange={(e) =\u003e onChange({ field: e.target.value })}\n            \u003e\n              \u003coption value=\"\"\u003eSelect field\u003c/option\u003e\n              {fields.map((f) =\u003e (\n                \u003coption key={f.value} value={f.value}\u003e{f.label}\u003c/option\u003e\n              ))}\n            \u003c/select\u003e\n\n            \u003cselect\n              value={rule.operator}\n              onChange={(e) =\u003e onChange({ operator: e.target.value })}\n            \u003e\n              {operators.map((op) =\u003e (\n                \u003coption key={op.value} value={op.value}\u003e{op.name}\u003c/option\u003e\n              ))}\n            \u003c/select\u003e\n\n            \u003cinput\n              value={rule.value ?? ''}\n              onChange={(e) =\u003e onChange({ value: e.target.value })}\n            /\u003e\n\n            \u003cbutton onClick={slots.onRemove}\u003eRemove\u003c/button\u003e\n          \u003c/div\u003e\n        )}\n        renderGroup={({ group, children, onChange, slots }) =\u003e (\n          \u003cdiv className=\"group\"\u003e\n            \u003cselect\n              value={group.combinator}\n              onChange={(e) =\u003e onChange({ combinator: e.target.value })}\n            \u003e\n              \u003coption value=\"and\"\u003eAND\u003c/option\u003e\n              \u003coption value=\"or\"\u003eOR\u003c/option\u003e\n            \u003c/select\u003e\n\n            \u003cbutton onClick={slots.onAddRule}\u003e+ Rule\u003c/button\u003e\n            \u003cbutton onClick={slots.onAddGroup}\u003e+ Group\u003c/button\u003e\n\n            \u003cdiv className=\"rules\"\u003e{children}\u003c/div\u003e\n          \u003c/div\u003e\n        )}\n      /\u003e\n    \u003c/QueryBuilder\u003e\n  );\n}\n```\n\n## With Drag \u0026 Drop\n\nUse `QueryBuilder.BuilderWithDnD` to enable drag-and-drop reordering.\n\n```tsx\nimport { QueryBuilder, type Query } from 'react-querybuilder-lite';\n\n\u003cQueryBuilder value={query} onChange={setQuery}\u003e\n  \u003cQueryBuilder.BuilderWithDnD\n    fields={fields}\n    renderRule={({ rule, fields, operators, onChange, slots }) =\u003e (\n      \u003cdiv className=\"rule\"\u003e\n        {/* Drag handle - spread slots.dragHandles on any element */}\n        \u003cspan className=\"drag-handle\" {...slots.dragHandles}\u003e⠿\u003c/span\u003e\n\n        \u003cselect value={rule.field} onChange={(e) =\u003e onChange({ field: e.target.value })}\u003e\n          {fields.map((f) =\u003e \u003coption key={f.value} value={f.value}\u003e{f.label}\u003c/option\u003e)}\n        \u003c/select\u003e\n\n        {/* ... rest of your UI */}\n      \u003c/div\u003e\n    )}\n    renderGroup={({ group, children, onChange, slots }) =\u003e (\n      \u003cdiv className=\"group\"\u003e\n        \u003cspan className=\"drag-handle\" {...slots.dragHandles}\u003e⠿\u003c/span\u003e\n\n        \u003cselect value={group.combinator} onChange={(e) =\u003e onChange({ combinator: e.target.value })}\u003e\n          \u003coption value=\"and\"\u003eAND\u003c/option\u003e\n          \u003coption value=\"or\"\u003eOR\u003c/option\u003e\n        \u003c/select\u003e\n\n        \u003cbutton onClick={slots.onAddRule}\u003e+ Rule\u003c/button\u003e\n        \u003cbutton onClick={slots.onAddGroup}\u003e+ Group\u003c/button\u003e\n\n        {children}\n      \u003c/div\u003e\n    )}\n  /\u003e\n\u003c/QueryBuilder\u003e\n```\n\n## API Reference\n\n### `\u003cQueryBuilder\u003e`\n\nRoot component that provides state management context.\n\n| Prop | Type | Required | Description |\n|------|------|----------|-------------|\n| `value` | `Query` | Yes | The query state |\n| `onChange` | `(query: Query) =\u003e void` | Yes | Called when query changes |\n| `maxDepth` | `number` | No | Maximum nesting depth. `1` = no nesting, `2` = one level, etc. |\n| `children` | `ReactNode` | Yes | Must contain `Builder` or `BuilderWithDnD` |\n\n### `\u003cQueryBuilder.Builder\u003e`\n\nRenders the query tree without drag-and-drop.\n\n| Prop | Type | Required | Description |\n|------|------|----------|-------------|\n| `fields` | `Field[]` | Yes | Available fields for rules |\n| `renderRule` | `(props: RuleRenderProps) =\u003e ReactNode` | Yes | Render function for rules |\n| `renderGroup` | `(props: GroupRenderProps) =\u003e ReactNode` | Yes | Render function for groups |\n| `operatorsByFieldType` | `Record\u003cFieldType, Operator[]\u003e` | No | Custom operator mapping |\n\n### `\u003cQueryBuilder.BuilderWithDnD\u003e`\n\nSame props as `Builder`, plus optional drag preview customization.\n\n| Prop | Type | Required | Description |\n|------|------|----------|-------------|\n| `renderDragPreview` | `(props: DragPreviewProps) =\u003e ReactNode` | No | Custom drag overlay |\n\n### Render Props\n\n#### `RuleRenderProps`\n\n```typescript\ninterface RuleRenderProps {\n  rule: Rule;                    // Current rule data\n  path: number[];                // Position in tree (e.g., [0, 1])\n  depth: number;                 // Nesting level\n  fields: Field[];               // Available fields\n  operators: Operator[];         // Operators for selected field type\n  selectedField?: Field;         // Currently selected field\n  selectedOperator?: Operator;   // Currently selected operator\n  slots: {\n    onRemove: () =\u003e void;        // Remove this rule\n    onClone: () =\u003e void;         // Duplicate this rule\n    onToggleLock: () =\u003e void;    // Toggle lock state\n    dragHandles: DragHandleType; // Spread on drag handle element\n  };\n  onChange: (updates: Partial\u003cRule\u003e) =\u003e void;  // Update rule\n}\n```\n\n#### `GroupRenderProps`\n\n```typescript\ninterface GroupRenderProps {\n  group: RuleGroup;              // Current group data\n  path: number[];                // Position in tree\n  depth: number;                 // Nesting level\n  children: ReactNode;           // Rendered child rules/groups\n  slots: {\n    onAddRule: () =\u003e void;       // Add rule to this group\n    onAddGroup: () =\u003e void;      // Add nested group\n    onRemove: () =\u003e void;        // Remove this group\n    onClone: () =\u003e void;         // Duplicate this group\n    onToggleLock: () =\u003e void;    // Toggle lock state\n    dragHandles: DragHandleType; // Spread on drag handle element\n  };\n  onChange: (updates: Partial\u003cRuleGroup\u003e) =\u003e void;  // Update group\n}\n```\n\n## Terminology\n\n| Term | Description |\n|------|-------------|\n| **Field** (or Column) | The data attribute you want to filter on. For example, \"First Name\", \"Age\", \"Created Date\" are fields. |\n| **Operator** | The comparison operation like \"equals\", \"contains\", \"greater than\". |\n| **Field Type** | Category of the field that determines available operators. Default types: `string`, `number`, `boolean`, `date`. You can define custom types. |\n| **Combinator** | Logical operator to combine rules: `AND` or `OR`. |\n\n## Types\n\n### Core Types\n\n```typescript\n// The root query structure\ntype Query = RuleGroup;\n\ninterface RuleGroup {\n  id: string;\n  combinator: 'and' | 'or';\n  rules: Array\u003cRule | RuleGroup\u003e;\n  isLocked?: boolean;\n}\n\ninterface Rule {\n  id: string;\n  field: string;\n  operator: OperatorKey;\n  value?: Value;\n  isLocked?: boolean;\n}\n\ninterface Field {\n  label: string;\n  value: string;\n  type: string;  // 'string' | 'number' | 'boolean' | 'date' or any custom type\n}\n```\n\n### Operators\n\nBuilt-in operators organized by type:\n\n| Type | Operators |\n|------|-----------|\n| Unary | `is_empty`, `is_not_empty`, `is_true`, `is_false` |\n| Binary | `equal`, `not_equal`, `less`, `less_or_equal`, `greater`, `greater_or_equal`, `contains`, `starts_with`, `ends_with` |\n| Range | `between`, `not_between` |\n| List | `in`, `not_in` |\n\nOperators are automatically filtered by field type:\n\n| Field Type | Available Operators |\n|------------|---------------------|\n| `string` | `is_empty`, `is_not_empty`, `equal`, `not_equal`, `contains`, `starts_with`, `ends_with`, `in`, `not_in` |\n| `number` | `is_empty`, `is_not_empty`, `equal`, `not_equal`, `less`, `less_or_equal`, `greater`, `greater_or_equal`, `between`, `not_between`, `in`, `not_in` |\n| `boolean` | `is_empty`, `is_not_empty`, `is_true`, `is_false` |\n| `date` | `is_empty`, `is_not_empty`, `equal`, `not_equal`, `less`, `greater`, `between`, `not_between`, `in`, `not_in` |\n\n## Custom Field Types\n\nYou're not limited to the default field types. Define your own types with custom operators:\n\n```typescript\nimport { QueryBuilder, type Query, type Operator } from 'react-querybuilder-lite';\n\n// Define fields with custom types\nconst fields = [\n  { label: 'Name', value: 'name', type: 'string' },\n  { label: 'Email', value: 'email', type: 'email' },           // Custom type\n  { label: 'Created', value: 'createdAt', type: 'datetime' },  // Custom type\n  { label: 'Price', value: 'price', type: 'currency' },        // Custom type\n];\n\n// Provide operators for your custom types\nconst operatorsByFieldType: Record\u003cstring, Operator[]\u003e = {\n  string: [\n    { name: 'Equals', value: 'equal', type: 'binary' },\n    { name: 'Contains', value: 'contains', type: 'binary' },\n  ],\n  email: [\n    { name: 'Is', value: 'equal', type: 'binary' },\n    { name: 'Contains', value: 'contains', type: 'binary' },\n    { name: 'Ends With', value: 'ends_with', type: 'binary' },\n  ],\n  datetime: [\n    { name: 'Before', value: 'less', type: 'binary' },\n    { name: 'After', value: 'greater', type: 'binary' },\n    { name: 'Between', value: 'between', type: 'range' },\n  ],\n  currency: [\n    { name: 'Equals', value: 'equal', type: 'binary' },\n    { name: 'Greater Than', value: 'greater', type: 'binary' },\n    { name: 'Less Than', value: 'less', type: 'binary' },\n    { name: 'Between', value: 'between', type: 'range' },\n  ],\n};\n\n\u003cQueryBuilder value={query} onChange={setQuery}\u003e\n  \u003cQueryBuilder.Builder\n    fields={fields}\n    operatorsByFieldType={operatorsByFieldType}\n    renderRule={...}\n    renderGroup={...}\n  /\u003e\n\u003c/QueryBuilder\u003e\n```\n\n## Localization (i18n)\n\nFull internationalization support. You control all user-facing text:\n\n- **`fields`** — Translated field labels\n- **`operatorsByFieldType`** — Translated operator names\n- **`renderRule` / `renderGroup`** — Your components, your language. Full control over buttons, placeholders, and combinators\n- **`dragDropAccessibility`** — Translated screen reader announcements\n\nSee the **[Localization story in Storybook →](https://nandhakumare.github.io/react-querybuilder-lite/?path=/docs/core-localization-i18n--docs)** for examples in Spanish, Japanese, and French. Need another language? Easy to configure refer to the comprehensive documentation in the story.\n\n## Live Demos\n\n**[📚 View Storybook →](https://nandhakumare.github.io/react-querybuilder-lite/)**\n\nInteractive examples showcasing all components with different configurations.\n\n## Design Decisions\n\n| Decision | Rationale |\n|----------|-----------|\n| Headless architecture | Maximum flexibility, framework agnostic |\n| Compound components | Implicit state sharing without prop drilling |\n| Path-based operations | O(depth) updates with structural sharing |\n| Render props over slots | Full control vs. limited customization |\n| Cascading lock state | UX: locked parent = locked children |\n| Optional DnD entry point | Respects bundle budgets |\n\n## Contributing\n\nContributions are welcome! Please open an issue or submit a PR.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnandhakumare%2Freact-querybuilder-lite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnandhakumare%2Freact-querybuilder-lite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnandhakumare%2Freact-querybuilder-lite/lists"}