---
title: Data Table Filter
description: Faceted filter bar for data tables with submenu-based option selection, text filters, and operator support.
icon: funnel
author:
  name: eblairmckee
  url: https://github.com/eblairmckee
search:
  keywords: [table filters, faceted filter]
related:
  - /data-table
  - /table
mode: wide
---

<PoweredBy
  packages={[
    { name: 'TanStack Table', url: 'https://tanstack.com/table/latest' },
    { name: 'Lucide', url: 'https://lucide.dev/' },
  ]}
/>

### Installation

Requires React and React DOM 19.2 or later. The copied item declares this API floor;
its text-filter debounce uses a local Effect Event so callback changes do not reset
the user's pending edit.

```package-install
npx shadcn@latest add @redpanda/data-table-filter
```

<ComponentPreview name="data-table-filter-demo" className="[&_.preview]:items-start" />

## When to use

Use this decision tree to determine when to use the Data Table Filter component:

```mermaid
graph TD;
    A[❓ Need to filter tabular data?] --> B{What kind of filtering?};
    B --> C[✅ Faceted filters with operators];
    B --> D[⚠️ Single text search only];
    B --> E[⚠️ Server-side filtering only];

    C --> F[❓ What filter types?];
    D --> G[⚠️ Use a plain Input instead];
    E --> H[ℹ️ Use with controlled state + API calls];

    F --> I[✅ Option / enum columns];
    F --> J[✅ Free-text columns];
    F --> K[✅ Multi-option columns];

    I --> L[❓ Need URL persistence?];
    J --> L;
    K --> L;
    H --> L;

    L --> M[✅ Use controlled mode + nuqs];
    L --> N[ℹ️ Uncontrolled mode is fine];

    classDef recommended fill:#4ade80,stroke:#22c55e,stroke-width:2px,color:#000;
    classDef discouraged fill:#fbbf24,stroke:#f59e0b,stroke-width:2px,color:#000;
    classDef decision fill:#60a5fa,stroke:#3b82f6,stroke-width:2px,color:#fff;
    classDef neutral fill:#9ca3af,stroke:#6b7280,stroke-width:2px,color:#fff;

    class C,I,J,K,M recommended;
    class D,E,G discouraged;
    class A,B,F,L decision;
    class H,N neutral;
```

Install the TanStack Table peer dependency if you haven't already:

```bash
npm install @tanstack/react-table
```

## Variants

Use the `variant` prop to style the filter trigger, active filter pills, and clear button with badge styling.

<ComponentPreview name="data-table-filter-variants-demo" className="[&_.preview]:items-start" />

## Keyboard and accessibility

Add, clear, operator, value, and remove actions are native buttons that do not submit a surrounding form. Operator and value controls announce the column they edit and describe the current operator or values. Open a control with Enter or Space, navigate its options with the arrow keys, select with Enter, and close with Escape to return focus to the trigger. Option checkmarks are decorative: the command option is the only selection target, and its description identifies values already included in the filter.

## Usage

```tsx
import { DataTableFilter, type FilterColumnConfig } from '@/components/redpanda-ui/data-table-filter';
import { useDataTable, type DataTableColumnDef } from '@/components/redpanda-ui/data-table';
import { useDataTableFilter } from '@/hooks/use-data-table-filter';
import { createFilterFn } from '@/lib/filter-utils';
```

### 1. Define filter columns

Describe which columns are filterable, what type of filter they use, and the available options:

```tsx
const filterColumns: FilterColumnConfig[] = [
  {
    id: 'status',
    type: 'option',
    displayName: 'Status',
    icon: CircleDotIcon,
    options: [
      { value: 'open', label: 'Open', icon: CircleIcon },
      { value: 'closed', label: 'Closed', icon: CircleCheckIcon },
    ],
  },
  {
    id: 'assignee',
    type: 'option',
    displayName: 'Assignee',
    icon: UserIcon,
    options: [
      { value: 'alice', label: 'Alice' },
      { value: 'bob', label: 'Bob' },
    ],
  },
  {
    id: 'title',
    type: 'text',
    displayName: 'Title',
    placeholder: 'Search titles…',
  },
];
```

### 2. Wire up TanStack Table columns

Each filterable table column needs a custom `filterFn` created with `createFilterFn`:

```tsx
import { createFilterFn } from '@/lib/filter-utils';

const tableColumns: DataTableColumnDef<Task>[] = [
  { accessorKey: 'title', header: 'Title', filterFn: createFilterFn('text') },
  { accessorKey: 'status', header: 'Status', filterFn: createFilterFn('option') },
  { accessorKey: 'assignee', header: 'Assignee', filterFn: createFilterFn('option') },
];
```

### 3. Connect the hook and render

```tsx
function TaskTable({ data }: { data: Task[] }) {
  const table = useDataTable({
    data,
    columns: tableColumns,
  });

  const { filters, actions } = useDataTableFilter<Task>({
    columns: filterColumns,
    table,
  });

  return (
    <div>
      <DataTableFilter actions={actions} columns={filterColumns} filters={filters} table={table} />
      {/* render your <Table /> below */}
    </div>
  );
}
```

## Filter types

| Type          | Operators                                                | Description                                                  |
| ------------- | -------------------------------------------------------- | ------------------------------------------------------------ |
| `text`        | `contains`, `does not contain`                           | Free-text search against a column value                      |
| `option`      | `is`, `is not`, `is any of`, `is none of`                | Single-select enum column with automatic operator transition |
| `multiOption` | `include`, `exclude`, `include any of`, `include all of` | Multi-value column (e.g. tags, labels)                       |

Operators auto-transition when the number of selected values changes. For example, selecting a second value in an `option` filter automatically switches from `is` to `is any of`.

### Text filters

Text filters provide free-text search with `contains` / `does not contain` operators.

<ComponentPreview name="data-table-filter-text-demo" className="[&_.preview]:items-start" />

## Controlled state

Use the `value` and `onValueChange` props on `useDataTableFilter` to manage filter state externally. This is useful for URL persistence, undo/redo, or syncing with external state.

<ComponentPreview name="data-table-filter-controlled-demo" className="[&_.preview]:items-start" />

## URL persistence with nuqs

Use [nuqs](https://nuqs.47ng.com/) to sync filter state with URL search params so filters survive page refreshes and can be shared via links:

```tsx
import { parseAsJson, useQueryState } from 'nuqs';
import { type FiltersState } from '@/lib/filter-utils';

function TaskTable({ data }: { data: Task[] }) {
  const [filters, setFilters] = useQueryState<FiltersState>('filters', parseAsJson<FiltersState>().withDefault([]));

  const { actions } = useDataTableFilter<Task>({
    columns: filterColumns,
    table,
    value: filters,
    onValueChange: setFilters,
  });

  return <DataTableFilter actions={actions} columns={filterColumns} filters={filters} table={table} />;
}
```

:::info
For TanStack Router projects, the [`useDataTableSearchParams`](/docs/data-table#url-state-with-tanstack-router) hook handles pagination, sorting, and filter URL sync out of the box.
:::

## API Reference

### `FilterColumnConfig`

Configuration for each filterable column.

| Prop                | Type                                  | Description                                              |
| ------------------- | ------------------------------------- | -------------------------------------------------------- |
| `id`                | `string`                              | Column ID matching a TanStack Table column `accessorKey` |
| `type`              | `"text" \| "option" \| "multiOption"` | Filter type                                              |
| `displayName`       | `string`                              | Human-readable label shown in the filter UI              |
| `displayNamePlural` | `string?`                             | Plural form for display (defaults to `displayName`)      |
| `icon`              | `ComponentType?`                      | Icon shown next to the filter name                       |
| `options`           | `FilterOption[]?`                     | Available options (for `option` and `multiOption` types) |
| `placeholder`       | `string?`                             | Placeholder text (for `text` type)                       |

### `useDataTableFilter<TData>`

Hook that manages filter state and syncs it to a TanStack Table instance.

| Option          | Type                              | Description                                                 |
| --------------- | --------------------------------- | ----------------------------------------------------------- |
| `columns`       | `FilterColumnConfig[]`            | Filter column definitions                                   |
| `table`         | `DataTableInstance<TData>?`       | Feature-bound TanStack Table V9 instance to sync filters to |
| `value`         | `FiltersState?`                   | Controlled filter state                                     |
| `onValueChange` | `(filters: FiltersState) => void` | Callback when filters change (controlled mode)              |
| `defaultValue`  | `FiltersState?`                   | Initial filter state (uncontrolled mode)                    |

Returns `{ filters, actions, columns }`.

### `DataTableFilterActions`

Actions returned by the hook for manipulating filter state.

| Method              | Signature                                      | Description                          |
| ------------------- | ---------------------------------------------- | ------------------------------------ |
| `addFilter`         | `(columnId: string) => void`                   | Add an empty filter for a column     |
| `removeFilter`      | `(columnId: string) => void`                   | Remove the filter for a column       |
| `removeAllFilters`  | `() => void`                                   | Clear all active filters             |
| `setFilterValues`   | `(columnId: string, values: string[]) => void` | Set the selected values for a filter |
| `addFilterValue`    | `(columnId: string, value: string) => void`    | Add a value to a filter              |
| `removeFilterValue` | `(columnId: string, value: string) => void`    | Remove a value from a filter         |
| `setFilterOperator` | `(columnId: string, operator: string) => void` | Change the operator for a filter     |

### `DataTableFilter`

The filter bar component.

| Prop        | Type                        | Description                                                                      |
| ----------- | --------------------------- | -------------------------------------------------------------------------------- |
| `columns`   | `FilterColumnConfig[]`      | Filter column definitions                                                        |
| `filters`   | `FiltersState`              | Current filter state                                                             |
| `actions`   | `DataTableFilterActions`    | Actions from `useDataTableFilter`                                                |
| `table`     | `DataTableInstance<TData>?` | Feature-bound TanStack Table V9 instance (enables faceted counts)                |
| `className` | `string?`                   | Additional CSS classes                                                           |
| `tone`      | `BadgeTone?`                | Semantic color shared by the trigger, pills, and clear button; default `primary` |
| `variant`   | `DataTableFilterVariant?`   | Emphasis: `solid`, `subtle` (default), or `outline`                              |

### `DataTableFilterVariant`

Controls emphasis for the trigger button, active pills, and clear button. Choose
`solid`, `subtle`, or `outline`, paired with a semantic `tone`.

The default remains the primary subtle treatment. Flat Badge style keys, including
`primary-inverted`, `default-outline`, and the disabled paint-only variants, are
removed. Disabled-looking paint is not an interaction-disabling API.

```tsx
<DataTableFilter
  actions={actions}
  columns={filterColumns}
  filters={filters}
  table={table}
  tone="default"
  variant="outline"
/>
```

## Additional patterns

### URL-synced filters

<ComponentPreview
  name="data-table-filter-nuqs-demo"
  description="Data-table filters synchronised to the URL for shareable, deep-linkable state."
/>

### Inline filter validation

<ComponentPreview
  name="data-table-filter-validation-demo"
  description="Data-table filters with inline validation (regex and range errors)."
/>

## Related

- [Filtered Table Pattern](/docs/patterns/filtered-table) — Full toolbar layout with DataTableFilter and table actions
- [Data Table](/docs/data-table) — TanStack Table-based data grid

## Credits

- Inspired by [bazza.dev Data Table Filter](https://ui.bazza.dev/docs/data-table-filter) for the filter architecture and operator model. See also: [ui.bazza.dev documentation](https://ui.bazza.dev/docs/data-table-filter).
- Uses [@tanstack/react-table](https://tanstack.com/table/latest) for faceted filtering and row model integration.
