Skip to content

DataTable

A composable, themeable and customizable data table component.

Open in
Product Name
Category
Brand
Price
Stock
Rating
In Stock
Release Date
Actions
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 all table state for demonstration
Search Query:None
Total Items:15
Selected Rows:0
Expanded Rows:0
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:
{}
Row Selection:
{}
Expanded Rows:
{}

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.

Browse Examples.

  1. Configure the @niko-table registry

    Add it under registries in components.json (merge with your existing config):

    components.json
    {
    "registries": {
    "@niko-table": "https://niko-table.com/r/{name}.json"
    }
    }
  2. Install a recommended starter set

    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

    Need more (virtualization, DnD, Data Grid)? See the Installation Guide or Components.

A DataTable 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 │ │ │
│ │ └─────────────┘ └───────────────────┘ └─────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
components/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,
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>
)
}

Let’s start with the most basic table. A simple table with data.

  1. Create your column definitions

    columns.tsx
    import type { DataTableColumnDef } from "@/components/niko-table/types"
    type User = {
    id: string
    name: string
    email: string
    }
    export const columns: DataTableColumnDef<User>[] = [
    {
    accessorKey: "name",
    header: "Name",
    },
    {
    accessorKey: "email",
    header: "Email",
    },
    ]
  2. 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>
    )
    }
  3. 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>
    )
    }
  4. 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>
    )
    }
  5. You’ve created your first table!

    Your table now has search functionality and pagination. See the Examples for more advanced configurations.

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.

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.

The config prop accepts a DataTableConfig object:

Name Type Default Description
enablePagination boolean false Enable pagination (auto-enabled when DataTablePagination is rendered).
enableFilters boolean false Enable filtering (auto-enabled when filter components are rendered).
enableSorting boolean false Enable sorting (auto-enabled when sort components are rendered).
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.

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>
)
}
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.

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>
)
}

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>
)
}
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>
)
}

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,
},
]
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.

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.

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 via useDataTable hook)
  • filters/ - Core filter implementation components (accept table prop directly, used by components/)
  • hooks/ - Custom React hooks for table functionality
  • lib/ - Utility functions and constants
  • types/ - TypeScript type definitions
  • config/ - Configuration and feature detection

This documentation follows this structure for easy navigation. Each component section includes links to source code and relevant documentation.

Essential building blocks of the data table. They handle table initialization, context management, and basic structure.

Components:

  • DataTableRoot - Provides table context and initializes TanStack Table
  • DataTable - Main table container with scrolling behavior
  • DataTableHeader - Table header with sortable columns
  • DataTableBody - Table body with rows and scroll events
  • DataTableSkeleton - Loading skeleton
  • DataTableEmptyBody - Empty state component
  • DataTableLoading - Loading indicator
  • DataTableErrorBoundary - 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 actions
  • DataTablePagination - Full-featured pagination controls
  • DataTableSearchFilter - Global search input with debouncing
  • DataTableFilterMenu - Command palette-style filter interface
  • DataTableFacetedFilter - Faceted filter for single/multiple selection
  • DataTableSortMenu - Sort management with drag-and-drop
  • DataTableViewMenu - Column visibility toggle
  • DataTableInlineFilter - Inline filter toolbar
  • DataTableSliderFilter - Slider filter for numeric ranges
  • DataTableDateFilter - Date filter component
  • DataTableClearFilter - Clear all filters button
  • DataTableExportButton - Export to CSV button
  • DataTableColumnHeader - Sortable column header
  • DataTableColumnFacetedFilterMenu - Column-level faceted filter popover
  • DataTableColumnSliderFilterMenu - Column-level slider filter popover
  • DataTableColumnDateFilterMenu - Column-level date filter popover
  • DataTableAside - Sidebar component
  • DataTableSelectionBar - Bulk actions bar
  • DataTableEmptyState - 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 filter
  • TablePagination - Core pagination
  • TableFilterMenu - Core filter menu
  • TableFacetedFilter - Core faceted filter
  • TableSliderFilter - Core slider filter
  • TableDateFilter - Core date filter
  • TableSortMenu - Core sort menu
  • TableViewMenu - Core view menu
  • TableInlineFilter - Core inline filter
  • TableClearFilter - Core clear filter
  • TableExportButton - Core export button
  • TableRangeFilter - Core range filter

Custom React hooks for table functionality.

Hooks:

  • useDataTable - Access table instance and context
  • useDebounce - Debounce values for search/filters
  • useDerivedColumnTitle - Derive column titles
  • useGeneratedOptions - Generate filter options from data
  • useKeyboardShortcut - Manage keyboard shortcuts

Niko Table is built on top of excellent open-source projects and inspired by the work of talented developers in the community.

  • 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.

  • 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.

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

MIT License - use it freely in your projects!