# Filter builder

> Nested and/or groups with operators by field type, native keyboard controls and focus that follows each add and remove; the model filters a data grid and is…

Language: en
Canonical: https://texuli.com/examples/data-filter-builder/
Markdown: https://texuli.com/examples/data-filter-builder.md (this page). Every texuli.com page has a Markdown twin at its URL plus `.md` (the home page is https://texuli.com/index.md), and any page URL answers `Accept: text/markdown` with its twin instead of HTML.
Last Updated: 2026-11-13

---

[All examples](https://texuli.com/examples/)

Nested and/or groups with operators by field type, native keyboard controls and focus that follows each add and remove; the model filters a data grid and is shown as JSON. Fictional deals.

[Filter builder](https://texuli.com/examples/data-filter-builder/)

`data-filter-builder` · `data/filter-builder`

 data filter

 Props
- `fields`
- `labels`
- `maxDepth`
- `value`

Live example loads in an isolated same-origin preview frame.

Source (data-filter-builder/FilterBuilderDemo.svelte)

```
<script lang="ts">
import {
  applyFilter,
  FilterBuilder,
  type FilterGroup,
  pruneFilter,
} from '@texuli/data/filter-builder';
import { DataGrid, type DataGridColumn } from '@texuli/data/grid-core';

interface Deal {
  id: string;
  name: string;
  stage: string;
  amount: number;
  renewal: boolean;
  closes: string;
}

// Fictional data only.
const deals: Deal[] = [
  {
    id: 'd1',
    name: 'Harbor Notes renewal',
    stage: 'Won',
    amount: 12000,
    renewal: true,
    closes: '2026-01-15',
  },
  {
    id: 'd2',
    name: 'Lumen Atlas pilot',
    stage: 'Open',
    amount: 4500,
    renewal: false,
    closes: '2026-03-02',
  },
  {
    id: 'd3',
    name: 'Quill & Pine expansion',
    stage: 'Lost',
    amount: 900,
    renewal: false,
    closes: '2026-02-10',
  },
  {
    id: 'd4',
    name: 'Café Ñandú rollout',
    stage: 'Open',
    amount: 30000,
    renewal: false,
    closes: '2026-04-20',
  },
  {
    id: 'd5',
    name: 'Northwind Labs seats',
    stage: 'Open',
    amount: 7800,
    renewal: true,
    closes: '2026-05-08',
  },
];
const columns: DataGridColumn<Deal>[] = [
  { key: 'name', header: 'Deal', type: 'text' },
  { key: 'stage', header: 'Stage', type: 'enum', options: ['Open', 'Won', 'Lost'] },
  { key: 'amount', header: 'Amount', type: 'number' },
  { key: 'renewal', header: 'Renewal', type: 'boolean' },
  { key: 'closes', header: 'Closes', type: 'date' },
];

let model = $state<FilterGroup>({
  type: 'group',
  op: 'and',
  children: [
    { type: 'condition', field: 'stage', operator: 'eq', value: 'Open' },
    {
      type: 'group',
      op: 'or',
      children: [
        { type: 'condition', field: 'amount', operator: 'between', value: [5000, 50000] },
        { type: 'condition', field: 'renewal', operator: 'eq', value: true },
      ],
    },
  ],
});
const runnable = $derived(pruneFilter(model, columns));
const count = $derived(applyFilter(deals, model, columns).length);
</script>

<section class="demo" data-example="data-filter-builder" aria-labelledby="data-filter-builder-title">
  <h2 id="data-filter-builder-title">Filter deals</h2>
  <p>
    Tab through the controls; each condition has a field, an operator and a value fitting the
    field's type. Adding moves focus to the new condition, removing returns it to the group.
  </p>
  <FilterBuilder fields={columns} bind:value={model} labels={{ builder: 'Deal filters' }} />
  <p>{count} of {deals.length} deals match.</p>
  <DataGrid
    rows={deals}
    {columns}
    getRowId={(deal) => deal.id}
    caption="Matching deals"
    filter={runnable}
    density="compact"
  />
  <details>
    <summary>Saved model (JSON)</summary>
    <pre>{JSON.stringify(model, null, 2)}</pre>
  </details>
</section>

<style>
  .demo {
    display: grid;
    gap: 0.75rem;
    max-inline-size: 56rem;
    padding: 1.5rem;
  }
  .demo h2,
  .demo p {
    margin: 0;
  }
  .demo pre {
    overflow: auto;
    max-block-size: 16rem;
    font-size: 0.8125rem;
  }
</style>
```

## For agents

A worked example rendered live in the browser; the isolated preview frame (`/examples/preview/<id>/`) shows the same example without the site shell, and the example page next to it carries its code. The live parts run in the browser and are not in this Markdown; the operations below do the same work through the read-only MCP server at https://texuli.com/mcp (no sign-in; see https://texuli.com/auth.md).

MCP operations ([reference](https://github.com/DailybotHQ/texuli/blob/main/packages/tools/docs/ops.md#operations)):

- `example.search`: Find component examples.
- `component.get`: Read a component manifest.

Docs:

- [Component docs for agents (llms.txt)](https://texuli.com/llms/llms.txt)
- [Catalog manifest snapshot (JSON)](https://texuli.com/catalog/catalog.json)
- [@texuli/examples README](https://github.com/DailybotHQ/texuli/blob/main/packages/examples/README.md)
- [texuli README](https://github.com/DailybotHQ/texuli/blob/main/packages/texuli/README.md)
