Faceted Filter Table
Add inline faceted filters that show available options and counts.
Preview with Controlled State
View Full State Object
[]
[]
{}{
"left": [],
"right": []
}Introduction
Section titled “Introduction”The Faceted Filter Table adds inline filter components that show available options with counts. Users can quickly see what filters are available and how many items match each option.
Installation
Section titled “Installation”Install the DataTable core and add-ons for this example:
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.
Prerequisites
Section titled “Prerequisites”We are going to build a table to show products with faceted filters. 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" },]Basic Table with Faceted Filters
Section titled “Basic Table with Faceted Filters”Let’s start by building a table with faceted filters.
Column Definitions
Section titled “Column Definitions”First, we’ll define our columns with filter functions.
"use client"
import { DataTableColumnHeader, DataTableColumnTitle,} from "@/components/niko-table/components/data-table-column-header"import { DataTableColumnSortMenu } from "@/components/niko-table/components/data-table-column-sort"import { DataTableColumnFacetedFilterMenu } from "@/components/niko-table/components/data-table-column-faceted-filter"import { DataTableColumnSliderFilterMenu } from "@/components/niko-table/components/data-table-column-slider-filter-options"import { DataTableColumnDateFilterMenu } from "@/components/niko-table/components/data-table-column-date-filter-options"import { FILTER_VARIANTS } from "@/components/niko-table/lib/constants"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}
export const columns: DataTableColumnDef<Product>[] = [ { accessorKey: "name", header: () => ( <DataTableColumnHeader> <DataTableColumnTitle /> <DataTableColumnSortMenu /> </DataTableColumnHeader> ), meta: { label: "Product Name" }, }, { accessorKey: "category", header: () => ( <DataTableColumnHeader> <DataTableColumnTitle /> <DataTableColumnSortMenu variant={FILTER_VARIANTS.TEXT} /> <DataTableColumnFacetedFilterMenu multiple limitToFilteredRows={false} /> </DataTableColumnHeader> ), meta: { label: "Category", options: categoryOptions, mergeStrategy: "augment", dynamicCounts: true, showCounts: true, }, enableColumnFilter: true, }, { accessorKey: "brand", header: () => ( <DataTableColumnHeader> <DataTableColumnTitle /> <DataTableColumnSortMenu variant={FILTER_VARIANTS.TEXT} /> <DataTableColumnFacetedFilterMenu limitToFilteredRows /> </DataTableColumnHeader> ), meta: { label: "Brand", autoOptions: true, dynamicCounts: true, showCounts: true, }, enableColumnFilter: true, }, { accessorKey: "price", header: () => ( <DataTableColumnHeader> <DataTableColumnTitle /> <DataTableColumnSortMenu variant={FILTER_VARIANTS.NUMBER} /> <DataTableColumnSliderFilterMenu /> </DataTableColumnHeader> ), meta: { label: "Price", unit: "$", variant: "range", // Auto-applies numberRangeFilter }, enableColumnFilter: true, }, { accessorKey: "releaseDate", header: () => ( <DataTableColumnHeader> <DataTableColumnTitle /> <DataTableColumnSortMenu /> <DataTableColumnDateFilterMenu /> </DataTableColumnHeader> ), meta: { label: "Release Date", variant: "dateRange", // Auto-applies dateRangeFilter }, enableColumnFilter: true, },]<DataTable /> component
Section titled “<DataTable /> component”Next, we’ll add faceted filter components.
"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 { DataTableFacetedFilter } from "@/components/niko-table/components/data-table-faceted-filter"import { DataTableSliderFilter } from "@/components/niko-table/components/data-table-slider-filter"import { DataTableDateFilter } from "@/components/niko-table/components/data-table-date-filter"import { DataTableClearFilter } from "@/components/niko-table/components/data-table-clear-filter"import { DataTableColumnHeader, DataTableColumnTitle,} from "@/components/niko-table/components/data-table-column-header"import { DataTableColumnSortMenu } from "@/components/niko-table/components/data-table-column-sort"import { DataTableColumnFacetedFilterMenu } from "@/components/niko-table/components/data-table-column-faceted-filter"import { DataTableColumnSliderFilterMenu } from "@/components/niko-table/components/data-table-column-slider-filter-options"import { DataTableColumnDateFilterMenu } from "@/components/niko-table/components/data-table-column-date-filter-options"import { FILTER_VARIANTS } from "@/components/niko-table/lib/constants"import type { DataTableColumnDef } from "@/components/niko-table/types"
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 columns: DataTableColumnDef<Product>[] = [ { accessorKey: "name", header: () => ( <DataTableColumnHeader> <DataTableColumnTitle /> <DataTableColumnSortMenu /> </DataTableColumnHeader> ), meta: { label: "Product Name" }, }, { accessorKey: "category", header: () => ( <DataTableColumnHeader> <DataTableColumnTitle /> <DataTableColumnSortMenu variant={FILTER_VARIANTS.TEXT} /> <DataTableColumnFacetedFilterMenu multiple limitToFilteredRows={false} /> </DataTableColumnHeader> ), meta: { label: "Category", options: categoryOptions, mergeStrategy: "augment", dynamicCounts: true, showCounts: true, }, enableColumnFilter: true, }, { accessorKey: "brand", header: () => ( <DataTableColumnHeader> <DataTableColumnTitle /> <DataTableColumnSortMenu variant={FILTER_VARIANTS.TEXT} /> <DataTableColumnFacetedFilterMenu limitToFilteredRows /> </DataTableColumnHeader> ), meta: { label: "Brand", autoOptions: true, dynamicCounts: true, showCounts: true, }, enableColumnFilter: true, }, { accessorKey: "price", header: () => ( <DataTableColumnHeader> <DataTableColumnTitle /> <DataTableColumnSortMenu variant={FILTER_VARIANTS.NUMBER} /> <DataTableColumnSliderFilterMenu /> </DataTableColumnHeader> ), meta: { label: "Price", unit: "$", variant: "range", }, enableColumnFilter: true, }, { accessorKey: "releaseDate", header: () => ( <DataTableColumnHeader> <DataTableColumnTitle /> <DataTableColumnSortMenu /> <DataTableColumnDateFilterMenu /> </DataTableColumnHeader> ), meta: { label: "Release Date", variant: "dateRange", }, enableColumnFilter: true, },]
export function FacetedFilterTable({ data }: { data: Product[] }) { return ( <DataTableRoot data={data} columns={columns}> <DataTableToolbarSection className="w-full flex-col justify-between gap-2"> <DataTableToolbarSection className="px-0"> <DataTableSearchFilter placeholder="Search products..." /> <DataTableViewMenu /> </DataTableToolbarSection> <DataTableToolbarSection className="flex-wrap px-0"> {/* Category: show all options (not limited by other filters) */} <DataTableFacetedFilter accessorKey="category" multiple limitToFilteredRows={false} /> {/* Brand: show only brands from filtered rows */} <DataTableFacetedFilter accessorKey="brand" limitToFilteredRows /> <DataTableSliderFilter accessorKey="price" /> <DataTableDateFilter accessorKey="releaseDate" multiple /> <DataTableClearFilter /> </DataTableToolbarSection> </DataTableToolbarSection>
<DataTable> <DataTableHeader /> <DataTableBody> <DataTableEmptyBody /> </DataTableBody> </DataTable>
<DataTablePagination /> </DataTableRoot> )}Faceted Filter Component
Section titled “Faceted Filter Component”DataTableFacetedFilter
Section titled “DataTableFacetedFilter”The faceted filter component shows available options with counts.
Props:
| Name | Type | Default | Description |
|---|---|---|---|
accessorKey | keyof TData & string | - | The column key to filter (required) |
title | string | - | Optional override (defaults to column meta.label or accessor name) |
options | Option[] | - | Static options; if omitted and column meta allows, options are auto-generated |
multiple | boolean | - | Allow multiple selections (defaults based on column variant) |
showCounts | boolean | true | Show counts next to options |
dynamicCounts | boolean | true | Counts reflect filtered rows |
limitToFilteredRows | boolean | true | Show only options from filtered rows (vs all rows) |
limitToFilteredRows
Section titled “limitToFilteredRows”The limitToFilteredRows prop controls whether the filter shows options from all data or only from the currently filtered rows.
When limitToFilteredRows={true} (default):
- Options are generated from rows that match other active filters
- Useful for filters like “Brand” or “Rating” where you want to see only relevant options
- Example: If you filter by “Category: Electronics”, the Brand filter will only show brands that exist in Electronics
When limitToFilteredRows={false}:
- Options are generated from all rows, regardless of other filters
- Useful for filters like “Category” where you want to see all available categories
- Example: Category filter always shows all categories, even when other filters are active
// Category: Show all options (not limited by other filters)<DataTableFacetedFilter accessorKey="category" title="Category" options={categoryOptions} multiple limitToFilteredRows={false} // Always show all categories/>
// Brand: Show only brands from filtered rows<DataTableFacetedFilter accessorKey="brand" title="Brand" multiple limitToFilteredRows={true} // Only show brands from current filter results/>// Auto-generated when meta.autoOptions or meta.options are set appropriately<DataTableFacetedFilter accessorKey="category" multiple />DataTableClearFilter
Section titled “DataTableClearFilter”Button to clear all active filters.
<DataTableClearFilter />Filter Functions
Section titled “Filter Functions”Note: For most use cases, you don’t need to define custom filterFn. The default extendedFilter handles the ExtendedColumnFilter format automatically. Only define a custom filterFn if you need special filtering logic.
If you do define a custom filterFn, it will work as you define it:
{ accessorKey: "category", enableColumnFilter: true, // Custom filter function (optional - default works for most cases) filterFn: (row, id, filterValue: string[]) => { if (!filterValue?.length) return true const rowValue = String(row.getValue(id)) return filterValue.includes(rowValue) },}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"orvariant: "dateRange"→dateRangeFilter(for date filtering)
{ accessorKey: "price", meta: { label: "Price", variant: "range", // Auto-applies numberRangeFilter unit: "$", }, // No need to define filterFn - it's auto-applied!}Multiple Selection
Section titled “Multiple Selection”Enable multiple selections with the multiple prop:
<DataTableFacetedFilter accessorKey="category" title="Category" options={categoryOptions} multiple // Allow selecting multiple categories/>Controlled State
Section titled “Controlled State”Manage filter state externally for full control:
import { useState } from "react"import type { PaginationState, SortingState, ColumnFiltersState, VisibilityState,} from "@tanstack/react-table"
export function ControlledFacetedFilterTable({ data }: { data: Product[] }) { const [globalFilter, setGlobalFilter] = useState<string | object>("") const [sorting, setSorting] = useState<SortingState>([]) const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]) const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({}) const [pagination, setPagination] = useState<PaginationState>({ pageIndex: 0, pageSize: 10, })
return ( <DataTableRoot data={data} columns={columns} state={{ globalFilter, sorting, columnFilters, columnVisibility, pagination, }} onGlobalFilterChange={value => { setGlobalFilter(value) setPagination(prev => ({ ...prev, pageIndex: 0 })) }} onSortingChange={setSorting} onColumnFiltersChange={filters => { setColumnFilters(filters) setPagination(prev => ({ ...prev, pageIndex: 0 })) }} onColumnVisibilityChange={setColumnVisibility} onPaginationChange={setPagination} > <DataTableToolbarSection className="w-full flex-col justify-between gap-2"> <DataTableToolbarSection className="px-0"> <DataTableSearchFilter placeholder="Search products..." /> <DataTableViewMenu /> </DataTableToolbarSection> <DataTableToolbarSection className="flex-wrap px-0"> <DataTableFacetedFilter accessorKey="category" multiple limitToFilteredRows={false} /> <DataTableFacetedFilter accessorKey="brand" limitToFilteredRows /> <DataTableSliderFilter accessorKey="price" /> <DataTableDateFilter accessorKey="releaseDate" multiple /> <DataTableClearFilter /> </DataTableToolbarSection> </DataTableToolbarSection>
<DataTable> <DataTableHeader /> <DataTableBody> <DataTableEmptyBody /> </DataTableBody> </DataTable>
<DataTablePagination /> </DataTableRoot> )}When to Use
Section titled “When to Use”✅ Use Faceted Filter Table when:
- You have categorical data (categories, tags, statuses)
- Users need to see available options and counts
- You want inline, visible filters
- Multiple filter options are common
❌ Consider other options when:
- You need complex rule-based filtering (use Advanced Table)
- You prefer dropdown filters (use Advanced Table)
- You only need search (use Search Table)
Next Steps
Section titled “Next Steps”- Advanced Table - Add advanced filtering menus
- Search Table - Add global search