DataTable
A composable, themeable and customizable data table component.
View Full State Object
No enhanced filters
{
"left": [],
"right": []
}{
"totalFilters": 0,
"hasAndFilters": false,
"hasOrFilters": false,
"effectiveJoinOperator": "and",
"activeFilters": 0
}[]
[]
""
{}{}{}A data table with sorting, filtering, pagination, row selection, and more.
Data tables are one of the most complex components to build. They are central to any application and often contain a lot of moving parts.
I don’t like building data tables. So I built 30+ of them. All kinds of configurations. Then I extracted the core components into data-table.
We now have a solid foundation to build on top of. Composable. Themeable. Customizable.
Installation
Section titled “Installation”-
Configure the
@niko-tableregistryAdd the registry to your
components.json:components.json {"registries": {"@niko-table": "https://niko-table.com/r/{name}.json"}} -
Install the DataTable core and all add-ons
For optional add-ons (pagination, filters, virtualization, etc.) and manual copy-paste, see the Installation Guide.
Structure
Section titled “Structure”A DataTable component is composed of the following parts:
DataTableRoot- The root provider that manages table state and context.DataTableToolbarSection- Container for filters, search, and actions.DataTable- The table container component.DataTableHeader- The table header with sortable columns.DataTableBody- The table body with rows.DataTablePagination- Pagination controls.
┌─────────────────────────────────────────────────────────────────┐│ DataTableRoot ││ ┌───────────────────────────────────────────────────────────┐ ││ │ DataTableToolbarSection │ ││ │ ┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐ │ ││ │ │ SearchFilter │ │ FilterMenu │ │ SortMenu/View │ │ ││ │ └─────────────────┘ └─────────────┘ └─────────────────┘ │ ││ └───────────────────────────────────────────────────────────┘ ││ ┌───────────────────────────────────────────────────────────┐ ││ │ DataTable │ ││ │ ┌─────────────────────────────────────────────────────┐ │ ││ │ │ DataTableHeader (sticky) │ │ ││ │ │ ┌─────────┬─────────┬─────────┬─────────────────┐ │ │ ││ │ │ │ Column │ Column │ Column │ Column │ │ │ ││ │ │ └─────────┴─────────┴─────────┴─────────────────┘ │ │ ││ │ └─────────────────────────────────────────────────────┘ │ ││ │ ┌─────────────────────────────────────────────────────┐ │ ││ │ │ DataTableBody (scrollable) │ │ ││ │ │ ┌─────────┬─────────┬─────────┬─────────────────┐ │ │ ││ │ │ │ Cell │ Cell │ Cell │ Cell │ │ │ ││ │ │ ├─────────┼─────────┼─────────┼─────────────────┤ │ │ ││ │ │ │ Cell │ Cell │ Cell │ Cell │ │ │ ││ │ │ └─────────┴─────────┴─────────┴─────────────────┘ │ │ ││ │ │ │ │ ││ │ │ DataTableSkeleton (when loading) │ │ ││ │ │ DataTableEmptyBody (when no data) │ │ ││ │ └─────────────────────────────────────────────────────┘ │ ││ └───────────────────────────────────────────────────────────┘ ││ ┌───────────────────────────────────────────────────────────┐ ││ │ DataTablePagination │ ││ │ ┌─────────────┐ ┌───────────────────┐ ┌─────────────┐ │ ││ │ │ Page Size │ │ Page 1 of 10 │ │ Navigation │ │ ││ │ └─────────────┘ └───────────────────┘ └─────────────┘ │ ││ └───────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────┘import { DataTableRoot } from "@/components/niko-table/core/data-table-root"import { DataTable } from "@/components/niko-table/core/data-table"import { DataTableHeader, DataTableBody, DataTableEmptyBody, DataTableSkeleton,} from "@/components/niko-table/core/data-table-structure"import { DataTableToolbarSection } from "@/components/niko-table/components/data-table-toolbar-section"import { DataTableSearchFilter } from "@/components/niko-table/components/data-table-search-filter"import { DataTablePagination } from "@/components/niko-table/components/data-table-pagination"import type { DataTableColumnDef } from "@/components/niko-table/types"
type User = { id: string name: string email: string}
const columns: DataTableColumnDef<User>[] = [ { accessorKey: "name", header: "Name" }, { accessorKey: "email", header: "Email" },]
export function UsersTable({ data }: { data: User[] }) { return ( <DataTableRoot data={data} columns={columns}> <DataTableToolbarSection> <DataTableSearchFilter placeholder="Search users..." /> </DataTableToolbarSection> <DataTable> <DataTableHeader /> <DataTableBody> <DataTableSkeleton /> <DataTableEmptyBody /> </DataTableBody> </DataTable> <DataTablePagination /> </DataTableRoot> )}Your First Table
Section titled “Your First Table”Let’s start with the most basic table. A simple table with data.
-
Create your column definitions
columns.tsx import type { DataTableColumnDef } from "@/components/niko-table/types"type User = {id: stringname: stringemail: string}export const columns: DataTableColumnDef<User>[] = [{accessorKey: "name",header: "Name",},{accessorKey: "email",header: "Email",},] -
Create your table component
users-table.tsx import { DataTableRoot } from "@/components/niko-table/core/data-table-root"import { DataTable } from "@/components/niko-table/core/data-table"import {DataTableHeader,DataTableBody,} from "@/components/niko-table/core/data-table-structure"import { columns } from "./columns"export function UsersTable({ data }: { data: User[] }) {return (<DataTableRoot data={data} columns={columns}><DataTable><DataTableHeader /><DataTableBody /></DataTable></DataTableRoot>)} -
Add loading and empty states
users-table.tsx import { DataTableRoot } from "@/components/niko-table/core/data-table-root"import { DataTable } from "@/components/niko-table/core/data-table"import {DataTableHeader,DataTableBody,DataTableSkeleton,DataTableEmptyBody,} from "@/components/niko-table/core/data-table-structure"export function UsersTable({data,isLoading,}: {data: User[]isLoading?: boolean}) {return (<DataTableRoot data={data} columns={columns} isLoading={isLoading}><DataTable><DataTableHeader /><DataTableBody><DataTableSkeleton /><DataTableEmptyBody /></DataTableBody></DataTable></DataTableRoot>)} -
Add search and pagination
users-table.tsx import { DataTableRoot } from "@/components/niko-table/core/data-table-root"import { DataTable } from "@/components/niko-table/core/data-table"import {DataTableHeader,DataTableBody,DataTableSkeleton,DataTableEmptyBody,} from "@/components/niko-table/core/data-table-structure"import { DataTableToolbarSection } from "@/components/niko-table/components/data-table-toolbar-section"import { DataTableSearchFilter } from "@/components/niko-table/components/data-table-search-filter"import { DataTablePagination } from "@/components/niko-table/components/data-table-pagination"export function UsersTable({data,isLoading,}: {data: User[]isLoading?: boolean}) {return (<DataTableRoot data={data} columns={columns} isLoading={isLoading}><DataTableToolbarSection><DataTableSearchFilter placeholder="Search users..." /></DataTableToolbarSection><DataTable><DataTableHeader /><DataTableBody><DataTableSkeleton /><DataTableEmptyBody /></DataTableBody></DataTable><DataTablePagination /></DataTableRoot>)} -
You’ve created your first table!
Your table now has search functionality and pagination. See the Examples for more advanced configurations.
Components
Section titled “Components”The components in data-table are built to be composable i.e you build your table by putting the provided components together. They also compose well with other shadcn/ui components such as DropdownMenu, Popover or Dialog etc.
If you need to change the code in data-table, you are encouraged to do so. The code is yours. Use data-table as a starting point and build your own.
See the Components page for detailed documentation on each component.
DataTableRoot
Section titled “DataTableRoot”The DataTableRoot component is used to provide the table context to all child components. You should always wrap your table in a DataTableRoot component.
| Name | Type | Description |
|---|---|---|
children | React.ReactNode | Child components (required). |
data | TData[] | The data array to display in the table. |
columns | DataTableColumnDef<TData>[] | Column definitions array. |
table | Table<TData> | Pre-configured TanStack Table instance (optional). |
config | DataTableConfig | Configuration object for feature toggles. |
state | Partial<TableState> | Controlled table state (pagination, sorting, etc). |
isLoading | boolean | Loading state for the table. |
getRowId | (row: TData, index: number) => string | Custom function to get row IDs. |
className | string | Additional CSS classes. |
onGlobalFilterChange | (value: GlobalFilter) => void | Callback when global filter changes. |
onPaginationChange | (updater: Updater<PaginationState>) => void | Callback when pagination changes. |
onSortingChange | (updater: Updater<SortingState>) => void | Callback when sorting changes. |
onColumnFiltersChange | (updater: Updater<ColumnFiltersState>) => void | Callback when column filters change. |
onColumnVisibilityChange | (updater: Updater<VisibilityState>) => void | Callback when column visibility changes. |
onRowSelectionChange | (updater: Updater<RowSelectionState>) => void | Callback when row selection changes. |
onExpandedChange | (updater: Updater<ExpandedState>) => void | Callback when expanded state changes. |
onRowSelection | (selectedRows: TData[]) => void | Callback with selected row data. |
Config
Section titled “Config”The config prop accepts a DataTableConfig object:
| Name | Type | Default | Description |
|---|---|---|---|
enablePagination | boolean | true | Enable pagination. |
enableFilters | boolean | true | Enable filtering. |
enableSorting | boolean | true | Enable sorting. |
enableRowSelection | boolean | false | Enable row selection. |
enableMultiSort | boolean | true | Enable multi-column sorting. |
enableGrouping | boolean | false | Enable column grouping. |
enableExpanding | boolean | false | Enable row expansion. |
manualSorting | boolean | false | Enable server-side sorting. |
manualPagination | boolean | false | Enable server-side pagination. |
manualFiltering | boolean | false | Enable server-side filtering. |
pageCount | number | - | Total pages (for server-side pagination). |
initialPageSize | number | 10 | Initial page size. |
initialPageIndex | number | 0 | Initial page index. |
autoResetPageIndex | boolean | - | Auto-reset page on filter/sort. Defaults to false when manualPagination: true. |
autoResetExpanded | boolean | true | Auto-reset expanded rows on filter/sort change. |
useDataTable
Section titled “useDataTable”The useDataTable hook is used to access the table instance from any child component.
import { useDataTable } from "@/components/niko-table/core/data-table-context"
export function CustomComponent() { const { table, isLoading } = useDataTable()
return ( <div> <p>Total rows: {table.getFilteredRowModel().rows.length}</p> <p>Loading: {isLoading ? "Yes" : "No"}</p> </div> )}Return Values
Section titled “Return Values”| Property | Type | Description |
|---|---|---|
table | DataTableInstance<TData> | The TanStack Table instance. |
columns | DataTableColumnDef<TData>[] | The column definitions. |
isLoading | boolean | Whether the table is in a loading state. |
setIsLoading | (isLoading: boolean) => void | Programmatically set the loading state. |
Controlled State
Section titled “Controlled State”Use the state callbacks to control the table externally:
import { useState } from "react"import type { SortingState, PaginationState } from "@tanstack/react-table"
export function ControlledTable({ data }: { data: User[] }) { const [sorting, setSorting] = useState<SortingState>([]) const [pagination, setPagination] = useState<PaginationState>({ pageIndex: 0, pageSize: 10, })
return ( <DataTableRoot data={data} columns={columns} state={{ sorting, pagination }} onSortingChange={setSorting} onPaginationChange={setPagination} > {/* ... */} </DataTableRoot> )}Server-Side Data
Section titled “Server-Side Data”For server-side pagination, sorting, and filtering:
export function ServerSideTable() { const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 })
const { data, totalCount, isLoading } = useQuery({ queryKey: ["users", pagination], queryFn: () => fetchUsers(pagination), })
const pageCount = Math.ceil(totalCount / pagination.pageSize)
return ( <DataTableRoot data={data ?? []} columns={columns} config={{ manualPagination: true, pageCount, }} isLoading={isLoading} onPaginationChange={setPagination} > <DataTable> <DataTableHeader /> <DataTableBody> <DataTableSkeleton /> <DataTableEmptyBody /> </DataTableBody> </DataTable> <DataTablePagination totalCount={totalCount} /> </DataTableRoot> )}Data Fetching
Section titled “Data Fetching”React Query
Section titled “React Query”function UsersTable() { const { data, isLoading } = useQuery({ queryKey: ["users"], queryFn: fetchUsers, })
return ( <DataTableRoot data={data ?? []} columns={columns} isLoading={isLoading}> <DataTable> <DataTableHeader /> <DataTableBody> <DataTableSkeleton /> <DataTableEmptyBody /> </DataTableBody> </DataTable> </DataTableRoot> )}function UsersTable() { const { data, isLoading } = useSWR("/api/users", fetcher)
return ( <DataTableRoot data={data ?? []} columns={columns} isLoading={isLoading}> <DataTable> <DataTableHeader /> <DataTableBody> <DataTableSkeleton /> <DataTableEmptyBody /> </DataTableBody> </DataTable> </DataTableRoot> )}Column Meta
Section titled “Column Meta”Columns support metadata for advanced filtering, sorting, and display:
const columns: DataTableColumnDef<Product>[] = [ { accessorKey: "name", header: () => ( <DataTableColumnHeader> <DataTableColumnTitle /> <DataTableColumnSortMenu /> </DataTableColumnHeader> ), meta: { label: "Product Name", placeholder: "Search products...", variant: "text", }, enableColumnFilter: true, enableSorting: 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: "range", unit: "$", }, enableColumnFilter: true, },]Filter Variants
Section titled “Filter Variants”| Variant | Description |
|---|---|
text | Text input filter. |
number | Number input filter. |
select | Single selection dropdown. |
multiSelect | Multiple selection dropdown. |
range | Numeric range slider. |
date | Single date picker. |
dateRange | Date range picker. |
boolean | Boolean toggle. |
Next Steps
Section titled “Next Steps”- Components - Detailed component documentation
- Simple Table - Basic table example
- Basic Table - Table with pagination and sorting
- Advanced Table - Complex filtering and sorting
- Server-Side Table - Server-side data handling
The components in data-table are built to be composable. You build your table by putting the provided components together. They also compose well with other shadcn/ui components.
If you need to change the code, you are encouraged to do so. The code is yours.
Directory Structure
Section titled “Directory Structure”The Niko Table components are organized into logical directories following the file structure in src/components/niko-table:
core/- Essential table components (DataTableRoot, DataTable, context, structure components)components/- User-facing context-aware components (automatically connect to table context viauseDataTablehook)filters/- Core filter implementation components (accepttableprop directly, used by components/)hooks/- Custom React hooks for table functionalitylib/- Utility functions and constantstypes/- TypeScript type definitionsconfig/- Configuration and feature detection
This documentation follows this structure for easy navigation. Each component section includes links to source code and relevant documentation.
Documentation Sections
Section titled “Documentation Sections”Essential building blocks of the data table. They handle table initialization, context management, and basic structure.
Components:
DataTableRoot- Provides table context and initializes TanStack TableDataTable- Main table container with scrolling behaviorDataTableHeader- Table header with sortable columnsDataTableBody- Table body with rows and scroll eventsDataTableSkeleton- Loading skeletonDataTableEmptyBody- Empty state componentDataTableLoading- Loading indicatorDataTableErrorBoundary- Error boundary for table- Virtualized components for large datasets
Context-aware components that automatically connect to the table via the useDataTable hook. These are the recommended components for most use cases.
Components:
DataTableToolbarSection- Container for filters and actionsDataTablePagination- Full-featured pagination controlsDataTableSearchFilter- Global search input with debouncingDataTableFilterMenu- Command palette-style filter interfaceDataTableFacetedFilter- Faceted filter for single/multiple selectionDataTableSortMenu- Sort management with drag-and-dropDataTableViewMenu- Column visibility toggleDataTableInlineFilter- Inline filter toolbarDataTableSliderFilter- Slider filter for numeric rangesDataTableDateFilter- Date filter componentDataTableClearFilter- Clear all filters buttonDataTableExportButton- Export to CSV buttonDataTableColumnHeader- Sortable column headerDataTableColumnFacetedFilterMenu- Column-level faceted filter popoverDataTableColumnSliderFilterMenu- Column-level slider filter popoverDataTableColumnDateFilterMenu- Column-level date filter popoverDataTableAside- Sidebar componentDataTableSelectionBar- Bulk actions barDataTableEmptyState- Empty state composition components
Core filter implementation components that accept a table prop directly. They are used internally by the context-aware components but can also be used standalone when building custom components.
Components:
TableSearchFilter- Core search filterTablePagination- Core paginationTableFilterMenu- Core filter menuTableFacetedFilter- Core faceted filterTableSliderFilter- Core slider filterTableDateFilter- Core date filterTableSortMenu- Core sort menuTableViewMenu- Core view menuTableInlineFilter- Core inline filterTableClearFilter- Core clear filterTableExportButton- Core export buttonTableRangeFilter- Core range filter
Custom React hooks for table functionality.
Hooks:
useDataTable- Access table instance and contextuseDebounce- Debounce values for search/filtersuseDerivedColumnTitle- Derive column titlesuseGeneratedOptions- Generate filter options from datauseKeyboardShortcut- Manage keyboard shortcuts
Credits and Inspirations
Section titled “Credits and Inspirations”Niko Table is built on top of excellent open-source projects and inspired by the work of talented developers in the community.
Core Dependencies
Section titled “Core Dependencies”-
TanStack Table by Tanner Linsley - The headless table library that powers everything. Provides the foundation for all table functionality including sorting, filtering, pagination, and more.
-
Shadcn UI by Shadcn - Beautiful, accessible component primitives built on Radix UI. All UI components in Niko Table are built using Shadcn UI components.
Major Inspirations
Section titled “Major Inspirations”-
sadmann7’s work - Major inspiration for filter components and table patterns:
- TableCN - Inspired our filter menu, inline filter, faceted filter, and slider filter implementations. The composition pattern and filter architecture drew heavily from this excellent project.
- DiceUI Sortable - Drag and drop sortable for row reordering, which inspired the sort menu implementation.
-
nuqs by François Best - Type-safe search params state manager for URL state management. Used in server-side examples for managing table state in URLs.
-
Web Dev Simplified Registry by Kyle Cook - Registry implementation pattern that inspired the structure and organization of this project.
Philosophy
Section titled “Philosophy”Following the Shadcn philosophy: “Nobody’s table, everyone’s solution.”
- Copy and paste the code into your project
- Own the code - modify it as needed
- No dependencies on external packages (except TanStack Table and Shadcn UI)
- Fully customizable and themeable
- Built with TypeScript for type safety
License
Section titled “License”MIT License - use it freely in your projects!