Skip to content

Advanced Filter Table

Rule-based filtering for complex datasets where users can define custom rules for data visibility.

Composes DataTableFilterMenu and DataTableSortMenu into a query-builder UX: users stack multiple typed filter rules (text, number, date, faceted) and combine them with AND/OR. State stays local — pair it with the Advanced Nuqs Table if you also need URL persistence.

Open in
Product Name
Category
Brand
Price
Stock
Rating
In Stock
Release Date
iPhone 15 ProElectronicsapple
$999.00
45
5
Yes7/10/2026
Galaxy S24 UltraElectronicssamsung
$1199.00
32
5
Yes7/5/2026
Air Jordan 1Sportsnike
$170.00
8
4
Yes6/20/2026
Ultraboost 23Sportsadidas
$190.00
15
4
Yes5/26/2026
PlayStation 5Electronicssony
$499.00
0
5
No7/15/2025
OLED C3 TVElectronicslg
$1499.00
12
5
Yes4/16/2026
XPS 15 LaptopElectronicsdell
$1899.00
20
4
Yes3/17/2026
Spectre x360Electronicshp
$1599.00
18
4
Yes6/30/2026
MacBook Pro 16Electronicsapple
$2499.00
25
5
Yes6/15/2026
Galaxy Book3Electronicssamsung
$1399.00
14
4
Yes1/16/2026
Preview with Controlled State
Open in
Product Name
Category
Brand
Price
Stock
Rating
In Stock
Release Date
iPhone 15 Pro
Electronicsapple
$999.00
45
5
Yes7/10/2026
Galaxy S24 Ultra
Electronicssamsung
$1199.00
32
5
Yes7/5/2026
Air Jordan 1
Sportsnike
$170.00
8
4
Yes6/20/2026
Ultraboost 23
Sportsadidas
$190.00
15
4
Yes5/26/2026
PlayStation 5
Electronicssony
$499.00
0
5
No7/15/2025
OLED C3 TV
Electronicslg
$1499.00
12
5
Yes4/16/2026
XPS 15 Laptop
Electronicsdell
$1899.00
20
4
Yes3/17/2026
Spectre x360
Electronicshp
$1599.00
18
4
Yes6/30/2026
MacBook Pro 16
Electronicsapple
$2499.00
25
5
Yes6/15/2026
Galaxy Book3
Electronicssamsung
$1399.00
14
4
Yes1/16/2026
Current Table State
Live view of the current table state for demonstration purposes
Search Query:None
Total Items:15
Active Filters:0
Enhanced Filters:0
Active Enhanced:0
Join Logic:and
Sorting:None
Page:1 (Size: 10)
Hidden Columns:0
Pinned Columns:0 Left, 0 Right
View Full State Object
Enhanced Filters:
No enhanced filters
Column Pinning:
{
  "left": [],
  "right": []
}
Filter Stats:
{
  "totalFilters": 0,
  "hasAndFilters": false,
  "hasOrFilters": false,
  "effectiveJoinOperator": "and",
  "activeFilters": 0
}
Filter Mode: AND
All conditions must match (stored in columnFilters)
Sorting:
[]
Column Filters State (AND logic):
[]
Global Filter State (OR logic):
""
Column Visibility:
{}

The Advanced Table demonstrates rule-based filtering with DataTableFilterMenu and DataTableSortMenu for complex filtering scenarios. Users can create multiple filter rules, combine them with AND/OR operators, and manage sorting with a visual interface.

Install the DataTable core and add-ons for this example:

pnpm dlx shadcn@latest add @niko-table/data-table @niko-table/data-table-pagination @niko-table/data-table-search-filter @niko-table/data-table-view-menu @niko-table/data-table-sort-menu @niko-table/data-table-filter-menu @niko-table/data-table-column-sort @niko-table/data-table-column-faceted-filter @niko-table/data-table-column-slider-filter @niko-table/data-table-column-date-filter

This example also uses checkbox from Shadcn UI:

pnpm dlx shadcn@latest add checkbox

First time using @niko-table? See the Installation Guide to set up the registry.

For other add-ons or manual copy-paste, see the Installation Guide.

We are going to build a table to show products with advanced filtering. Here’s what our data looks like:

type Product = {
id: string
name: string
category: string
brand: string
price: number
stock: number
rating: number
inStock: boolean
releaseDate: Date
}
const categoryOptions = [
{ label: "Electronics", value: "electronics" },
{ label: "Clothing", value: "clothing" },
{ label: "Sports", value: "sports" },
]
// Brand options can be auto-generated from data using meta.autoOptions

Let’s start by building a table with rule-based filtering.

First, we’ll define our columns with filter metadata.

columns.tsx
"use client"
import { DataTableColumnHeader } from "@/components/niko-table/components/data-table-column-header"
import { DataTableColumnTitle } from "@/components/niko-table/components/data-table-column-title"
import { DataTableColumnSortMenu } from "@/components/niko-table/components/data-table-column-sort"
import type { DataTableColumnDef } from "@/components/niko-table/types"
export type Product = {
id: string
name: string
category: string
brand: string
price: number
stock: number
rating: number
inStock: boolean
releaseDate: Date
}
const categoryOptions = [
{ label: "Electronics", value: "electronics" },
{ label: "Clothing", value: "clothing" },
{ label: "Sports", value: "sports" },
]
const brandOptions = [
{ label: "Apple", value: "apple" },
{ label: "Samsung", value: "samsung" },
{ label: "Nike", value: "nike" },
]
export const columns: DataTableColumnDef<Product>[] = [
{
accessorKey: "name",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Product Name",
variant: "text",
},
enableColumnFilter: true,
},
{
accessorKey: "category",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Category",
variant: "select",
options: categoryOptions,
mergeStrategy: "augment",
dynamicCounts: true,
showCounts: true,
},
enableColumnFilter: true,
},
{
accessorKey: "brand",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Brand",
variant: "select",
autoOptions: true,
dynamicCounts: true,
showCounts: true,
},
enableColumnFilter: true,
},
{
accessorKey: "price",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Price",
variant: "number",
unit: "$",
},
enableColumnFilter: true,
},
]

Next, we’ll add the filter and sort menu components.

advanced-table.tsx
"use client"
import { DataTableRoot } from "@/components/niko-table/core/data-table-root"
import { DataTable } from "@/components/niko-table/core/data-table"
import {
DataTableHeader,
DataTableBody,
DataTableEmptyBody,
} from "@/components/niko-table/core/data-table-structure"
import { DataTableToolbarSection } from "@/components/niko-table/components/data-table-toolbar-section"
import { DataTablePagination } from "@/components/niko-table/components/data-table-pagination"
import { DataTableSearchFilter } from "@/components/niko-table/components/data-table-search-filter"
import { DataTableViewMenu } from "@/components/niko-table/components/data-table-view-menu"
import { DataTableSortMenu } from "@/components/niko-table/components/data-table-sort-menu"
import { DataTableFilterMenu } from "@/components/niko-table/components/data-table-filter-menu"
import { DataTableColumnHeader } from "@/components/niko-table/components/data-table-column-header"
import { DataTableColumnTitle } from "@/components/niko-table/components/data-table-column-title"
import { DataTableColumnSortMenu } from "@/components/niko-table/components/data-table-column-sort"
import type { DataTableColumnDef } from "@/components/niko-table/types"
type Product = {
id: string
name: string
category: string
brand: string
price: number
}
const columns: DataTableColumnDef<Product>[] = [
{
accessorKey: "name",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Product Name",
variant: "text",
},
enableColumnFilter: true,
},
{
accessorKey: "category",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Category",
variant: "select",
options: [
{ label: "Electronics", value: "electronics" },
{ label: "Clothing", value: "clothing" },
],
},
enableColumnFilter: true,
},
{
accessorKey: "price",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: {
label: "Price",
variant: "number",
unit: "$",
},
enableColumnFilter: true,
},
]
function FilterToolbar() {
return (
<DataTableToolbarSection>
<DataTableToolbarSection className="px-0">
<DataTableSearchFilter placeholder="Search products..." />
<DataTableViewMenu />
</DataTableToolbarSection>
<DataTableToolbarSection className="px-0">
<DataTableSortMenu className="ml-auto" />
<DataTableFilterMenu
autoOptions
dynamicCounts
showCounts
mergeStrategy="augment"
/>
</DataTableToolbarSection>
</DataTableToolbarSection>
)
}
export function AdvancedTable({ data }: { data: Product[] }) {
return (
<DataTableRoot
data={data}
columns={columns}
config={{
enablePagination: true,
enableSorting: true,
enableMultiSort: true,
enableFilters: true,
}}
>
<FilterToolbar />
<DataTable>
<DataTableHeader />
<DataTableBody>
<DataTableEmptyBody />
</DataTableBody>
</DataTable>
<DataTablePagination />
</DataTableRoot>
)
}

The filter menu component allows users to create rule-based filters with multiple conditions.

Props:

Name Type Default Description
autoOptions boolean true Auto-generate options for select/multiSelect columns
showCounts boolean true Show counts beside each option
dynamicCounts boolean true Counts recompute at the provider level automatically; prop kept for API parity
limitToFilteredRows boolean true Generate options from filtered rows only (vs all rows)
includeColumns string[] - Only generate options for these column ids
excludeColumns string[] - Exclude these column ids from generation
limitPerColumn number - Limit number of generated options per column
mergeStrategy "preserve" | "augment" | "replace" "preserve" How to handle existing static options
filters? ExtendedColumnFilter[] - Array of filters (for controlled mode)
onFiltersChange? (filters: ExtendedColumnFilter[] | null) => void - Callback when filters change
className string - Additional CSS classes

Merge Strategy:

  • preserve: Keep user-defined options untouched (default)
  • augment: Add counts to matching values from static options
  • replace: Override static options with generated options
<DataTableFilterMenu
autoOptions
dynamicCounts
showCounts
limitToFilteredRows={true}
mergeStrategy="augment"
/>

The sort menu component provides a visual interface for managing column sorting.

Props:

Name Type Description
className string Additional CSS classes
<DataTableSortMenu className="ml-auto" />

Define filter metadata in your column definitions:

{
accessorKey: "category",
meta: {
label: "Category",
variant: "select",
options: [
{ label: "Electronics", value: "electronics" },
{ label: "Clothing", value: "clothing" },
],
},
enableColumnFilter: true,
}

Supported variants:

  • "text" - Text input filter
  • "select" - Dropdown select filter (requires options)
  • "multiSelect" - Multiple selection dropdown
  • "number" - Number input filter (optional unit for display)
  • "range" - Numeric range slider (auto-applied for number variant with meta.range)
  • "boolean" - Boolean checkbox filter
  • "date" - Single date picker filter
  • "dateRange" - Date range picker (auto-applied when DataTableDateFilter has multiple={true})

Auto-applied filter functions:

When you specify meta.variant in your column definition, the appropriate filterFn is automatically applied:

  • variant: "range"numberRangeFilter (for numeric ranges like price)
  • variant: "date" or variant: "dateRange"dateRangeFilter (for date filtering)

You can still provide a custom filterFn if needed - it will override the auto-applied function.

Full control over table state:

import { useState } from "react"
import type {
PaginationState,
SortingState,
ColumnFiltersState,
} from "@tanstack/react-table"
import type { ExtendedColumnFilter } from "@/components/niko-table/types"
export function ControlledAdvancedTable({ data }: { data: Product[] }) {
const [globalFilter, setGlobalFilter] = useState<string | object>("")
const [sorting, setSorting] = useState<SortingState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
})
const handleFiltersChange = (
filters: ExtendedColumnFilter<Product>[] | null,
) => {
if (!filters || filters.length === 0) {
setColumnFilters([])
} else {
setColumnFilters(
filters.map(filter => ({
id: filter.id,
value: filter,
})),
)
}
}
return (
<DataTableRoot
data={data}
columns={columns}
config={{
enablePagination: true,
enableSorting: true,
enableMultiSort: true,
enableFilters: true,
}}
state={{
globalFilter,
sorting,
columnFilters,
pagination,
}}
onGlobalFilterChange={setGlobalFilter}
onSortingChange={setSorting}
onColumnFiltersChange={setColumnFilters}
onPaginationChange={setPagination}
>
<DataTableToolbarSection>
<DataTableToolbarSection className="px-0">
<DataTableSearchFilter placeholder="Search products..." />
<DataTableViewMenu />
</DataTableToolbarSection>
<DataTableToolbarSection className="px-0">
<DataTableSortMenu className="ml-auto" />
<DataTableFilterMenu
filters={[]}
onFiltersChange={handleFiltersChange}
/>
</DataTableToolbarSection>
</DataTableToolbarSection>
<DataTable>
<DataTableHeader />
<DataTableBody>
<DataTableEmptyBody />
</DataTableBody>
</DataTable>
<DataTablePagination />
</DataTableRoot>
)
}

✅ Use Advanced Table when:

  • Users need complex, rule-based filtering
  • You want visual filter and sort management
  • Multiple filter conditions need to be combined
  • You need drag-and-drop sorting interface
  • Filter state needs to be managed externally

❌ Consider other options when: