Skip to content

Advanced Filter Table

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

Open in
Preview with Controlled State
Open in

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.

  1. Add the required components:
npx shadcn@latest add table input button dropdown-menu popover command checkbox select scroll-area separator skeleton tooltip
  1. Add tanstack/react-table dependency:
npm install @tanstack/react-table

Required: Sortable Component

This example uses DataTableSortMenu and DataTableFilterMenu which require the Sortable component for drag-and-drop reordering. Follow the DiceUI Sortable installation guide.

  1. Copy the DataTable components into your project. See the Installation Guide for detailed instructions.

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,
DataTableColumnTitle,
DataTableColumnSortMenu,
} from "@/components/niko-table/components"
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,
DataTable,
DataTableHeader,
DataTableBody,
DataTableEmptyBody,
} from "@/components/niko-table/core"
import {
DataTableToolbarSection,
DataTablePagination,
DataTableSearchFilter,
DataTableViewMenu,
DataTableSortMenu,
DataTableFilterMenu,
DataTableColumnHeader,
DataTableColumnTitle,
DataTableColumnSortMenu,
} from "@/components/niko-table/components"
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:

NameTypeDefaultDescription
autoOptionsbooleantrueAuto-generate options for select/multiSelect columns
showCountsbooleantrueShow counts beside each option
dynamicCountsbooleantrueRecompute counts based on currently filtered rows
limitToFilteredRowsbooleantrueGenerate options from filtered rows only (vs all rows)
includeColumnsstring[]-Only generate options for these column ids
excludeColumnsstring[]-Exclude these column ids from generation
limitPerColumnnumber-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
classNamestring-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:

NameTypeDescription
classNamestringAdditional 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
  • "date_range" - 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: "date_range"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: