Skip to content

Core Components

Essential building blocks of the data table including DataTableRoot, DataTable, and structure components.

Core components are the essential building blocks of the data table. They handle table initialization, context management, and basic structure.

The DataTableRoot component provides the table context to all child components. You should always wrap your table in a DataTableRoot. It initializes the TanStack Table instance and manages table state.

import { DataTableRoot } from "@/components/niko-table/core/data-table-root"
export function MyTable({ data }: { data: User[] }) {
return (
<DataTableRoot data={data} columns={columns}>
{/* child components */}
</DataTableRoot>
)
}
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.
isLoading boolean Loading state for the table.
getRowId (row: TData, index: number) => string Custom function to get row IDs.
className string Additional CSS classes.
state Partial<TableState> Controlled table state (pagination, sorting, etc).
Name Type Description
onGlobalFilterChange (value: GlobalFilter) => void Called when global filter changes.
onPaginationChange (updater: Updater<PaginationState>) => void Called when pagination changes.
onSortingChange (updater: Updater<SortingState>) => void Called when sorting changes.
onColumnFiltersChange (updater: Updater<ColumnFiltersState>) => void Called when column filters change.
onColumnVisibilityChange (updater: Updater<VisibilityState>) => void Called when column visibility changes.
onRowSelectionChange (updater: Updater<RowSelectionState>) => void Called when row selection changes.
onExpandedChange (updater: Updater<ExpandedState>) => void Called when expanded state changes.
onColumnPinningChange (updater: Updater<ColumnPinningState>) => void Called when column pinning changes.
onColumnOrderChange (updater: Updater<ColumnOrderState>) => void Called when column order changes.
onRowSelection (selectedRows: TData[]) => void Called with selected row data.

The config prop accepts a DataTableConfig object with the following properties:

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 (required for server-side pagination).
initialPageSize number 10 Initial page size.
initialPageIndex number 0 Initial page index.
autoResetPageIndex boolean - Auto-reset page on filter/sort change. Defaults to false when manualPagination: true, otherwise true.
autoResetExpanded boolean true Auto-reset expanded rows on filter/sort change.

Low-level context provider used internally by DataTableRoot. Use this directly when you need to provide a pre-configured TanStack Table instance to the context (e.g., when using useReactTable yourself).

import { DataTableProvider } from "@/components/niko-table/core/data-table-context"
import { useReactTable, getCoreRowModel } from "@tanstack/react-table"
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
<DataTableProvider table={table} columns={columns} isLoading={false}>
{/* child components can use useDataTable() */}
</DataTableProvider>
Name Type Description
table DataTableInstance<TData> TanStack Table instance (required).
columns DataTableColumnDef<TData>[] Column definitions (optional).
isLoading boolean Loading state (optional).
children React.ReactNode Child components.

The raw React context object. Useful for advanced scenarios where you need direct access to the context (e.g., conditional rendering based on context availability).

import { DataTableContext } from "@/components/niko-table/core/data-table-context"
import { useContext } from "react"
const context = useContext(DataTableContext)

The main table container component that wraps the table structure and provides scrolling behavior.

import { DataTable } from "@/components/niko-table/core/data-table"
<DataTable className="rounded-md border" height={600}>
<DataTableHeader />
<DataTableBody />
</DataTable>
Name Type Default Description
className string - Additional CSS classes. Height utilities (e.g., h-[600px]) are automatically extracted and applied.
children React.ReactNode - Child components (Header, Body).
height number | string - Sets the height of the table container. Enables vertical scrolling and scroll event callbacks.
maxHeight number | string - Sets the maximum height of the table container. Defaults to the height value if not specified.

Renders the table header with sortable columns.

import { DataTableHeader } from "@/components/niko-table/core/data-table-structure"
<DataTableHeader sticky={true} />
Name Type Default Description
className string - Additional CSS classes.
sticky boolean true Makes the header sticky at the top when scrolling.

Renders the table body with rows. Supports scroll events and row expansion.

import {
DataTableBody,
DataTableSkeleton,
DataTableEmptyBody,
} from "@/components/niko-table/core/data-table-structure"
<DataTableBody
onScroll={e => console.log(`Scrolled ${e.percentage}%`)}
onScrolledBottom={() => loadMore()}
>
<DataTableSkeleton />
<DataTableEmptyBody />
</DataTableBody>
Name Type Default Description
className string - Additional CSS classes.
children React.ReactNode - Child components (Skeleton, EmptyBody).
onScroll (event: ScrollEvent) => void - Scroll event handler.
onScrolledTop () => void - Called when scrolled to top.
onScrolledBottom () => void - Called when scrolled to bottom.
scrollThreshold number 50 Threshold for scroll events.
onRowClick (row: TData) => void - Called when a row is clicked.
getRowMemoKey (row: TData) => string - Per-row key encoding external row state (inline edits, optimistic overlays). When it changes for a row, only that row re-renders. See Inline Edit Table.

Row memo invalidation: all six body components are wrapped in React.memo for performance. Column toggle / reorder / pin already invalidate rows (the columnLayoutSignature prop encodes them). If your column cell functions read external state (inline edit drafts, optimistic overlays) wire getRowMemoKey so that state change forces only the affected row to re-render.

The ScrollEvent object passed to onScroll contains:

Property Type Description
scrollTop number Current scroll position from top.
scrollHeight number Total scrollable height.
clientHeight number Visible height of the container.
isTop boolean Whether scrolled to the top.
isBottom boolean Whether scrolled to the bottom.
percentage number Scroll percentage (0-100).

Shows a loading skeleton when isLoading is true. Automatically matches the table structure.

<DataTableBody>
<DataTableSkeleton rows={5} />
</DataTableBody>
Name Type Default Description
rows number 5 Number of skeleton rows to display.
colSpan number - Number of columns (auto-detected if not provided).
className string - Additional CSS classes.
cellClassName string - CSS classes for skeleton cells.
skeletonClassName string - CSS classes for skeleton elements.
children React.ReactNode - Custom skeleton content (replaces rows).

Shows an empty state when there’s no data. Uses composition pattern with DataTableEmpty* components.

<DataTableBody>
<DataTableEmptyBody>
<DataTableEmptyIcon>
<PackageOpen className="size-12" />
</DataTableEmptyIcon>
<DataTableEmptyMessage>
<DataTableEmptyTitle>No products found</DataTableEmptyTitle>
<DataTableEmptyDescription>
Get started by adding your first product
</DataTableEmptyDescription>
</DataTableEmptyMessage>
<DataTableEmptyFilteredMessage>
No results match your filters
</DataTableEmptyFilteredMessage>
<DataTableEmptyActions>
<Button onClick={handleAdd}>Add Product</Button>
</DataTableEmptyActions>
</DataTableEmptyBody>
</DataTableBody>
Name Type Default Description
children React.ReactNode - Empty state composition components.
colSpan number - Number of columns (auto-detected if not provided).
className string - Additional CSS classes.

Shows a loading indicator when isLoading is true.

<DataTableBody>
<DataTableLoading>
<Spinner />
</DataTableLoading>
</DataTableBody>
Name Type Default Description
children React.ReactNode - Custom loading content.
colSpan number - Number of columns (auto-detected if not provided).
className string - Additional CSS classes.

Composable “loading more” row for infinite-scroll tables. Renders at the end of the body when isFetching is true, and nothing when false. Designed to be dropped as a child of DataTableBody alongside DataTableSkeleton and DataTableEmptyBody, combined with onScrolledBottom on the body to trigger next-page fetches.

<DataTableBody
onScrolledBottom={() => {
if (hasMore && !isFetching) void loadMore()
}}
>
<DataTableSkeleton rows={10} />
<DataTableEmptyBody />
<DataTableLoadingMore isFetching={isFetching}>
Loading more products...
</DataTableLoadingMore>
</DataTableBody>

See Infinite Scroll Table for a complete working example.

Name Type Default Description
isFetching boolean - Required. Whether a next-page fetch is currently in flight. Self-gates on false.
children React.ReactNode "Loading more…" Custom label content (e.g. "Loading more products…").
colSpan number - Number of columns (auto-detected if not provided).
className string - Additional CSS classes on the spinner cell.

Error boundary component that catches errors in the data table component tree and displays a fallback UI.

import { DataTableErrorBoundary } from "@/components/niko-table/core/data-table-error-boundary"
<DataTableErrorBoundary
onError={(error, errorInfo) => {
console.error("Table error:", error)
trackError(error)
}}
>
<DataTableRoot data={data} columns={columns}>
{/* table components */}
</DataTableRoot>
</DataTableErrorBoundary>
Name Type Default Description
children React.ReactNode - The content to render when there’s no error.
fallback React.ReactNode - Custom fallback UI to show when an error occurs.
onError (error: Error, errorInfo: React.ErrorInfo) => void - Callback fired when an error is caught.
showResetButton boolean true Whether to show a reset button.
resetButtonText string "Try Again" Custom reset button text.

For large datasets (1000+ rows), use the virtualized variants for optimal performance.

Virtualized table header component.

import {
DataTableVirtualizedHeader,
DataTableVirtualizedBody,
} from "@/components/niko-table/core/data-table-virtualized-structure"
<DataTable>
<DataTableVirtualizedHeader />
<DataTableVirtualizedBody estimateSize={34} overscan={20}>
{/* rows */}
</DataTableVirtualizedBody>
</DataTable>
Name Type Default Description
className string - Additional CSS classes.
sticky boolean true Makes the header sticky at the top when scrolling.

Virtualized table body for rendering thousands of rows efficiently.

Name Type Default Description
children React.ReactNode - Child components (VirtualizedSkeleton, VirtualizedEmptyBody).
estimateSize number 34 Estimated row height in pixels.
overscan number 20 Number of items to render outside visible area.
className string - Additional CSS classes.
onScroll (event: ScrollEvent) => void - Scroll event handler.
onScrolledTop () => void - Called when scrolled to top.
onScrolledBottom () => void - Called when scrolled to bottom (scroll-event-driven).
scrollThreshold number 50 Pixel threshold for scroll events.
onNearEnd () => void - Fires when the last rendered virtual row is within prefetchThreshold rows of the end. Virtualizer-index-driven — strictly better than onScrolledBottom for infinite scroll.
prefetchThreshold number 10 How many rows from the end to trigger onNearEnd.
onRowClick (row: TData, event: React.MouseEvent) => void - Called when a row is clicked.
getRowMemoKey (row: TData) => string - Per-row key encoding external row state. When it changes for a row, only that row (and its size measurement) re-renders.

Empty state component for virtualized tables.

import {
DataTableVirtualizedBody,
DataTableVirtualizedEmptyBody,
} from "@/components/niko-table/core/data-table-virtualized-structure"
import {
DataTableEmptyIcon,
DataTableEmptyMessage,
DataTableEmptyTitle,
DataTableEmptyDescription,
} from "@/components/niko-table/components/data-table-empty-state"
import { PackageOpen } from "lucide-react"
<DataTableVirtualizedBody estimateSize={34}>
<DataTableVirtualizedEmptyBody>
<DataTableEmptyIcon>
<PackageOpen className="size-12" />
</DataTableEmptyIcon>
<DataTableEmptyMessage>
<DataTableEmptyTitle>No data found</DataTableEmptyTitle>
<DataTableEmptyDescription>
Get started by adding your first item
</DataTableEmptyDescription>
</DataTableEmptyMessage>
</DataTableVirtualizedEmptyBody>
</DataTableVirtualizedBody>
Name Type Default Description
children React.ReactNode - Empty state composition components.
colSpan number - Number of columns (auto-detected if not provided).
className string - Additional CSS classes.

Loading skeleton for virtualized tables.

import {
DataTableVirtualizedBody,
DataTableVirtualizedSkeleton,
} from "@/components/niko-table/core/data-table-virtualized-structure"
<DataTableVirtualizedBody estimateSize={34}>
<DataTableVirtualizedSkeleton rows={10} estimateSize={34} />
</DataTableVirtualizedBody>
Name Type Default Description
rows number 5 Number of skeleton rows to display.
estimateSize number 34 Estimated row height (should match VirtualizedBody).
className string - Additional CSS classes.
cellClassName string - CSS classes for skeleton cells.
skeletonClassName string - CSS classes for skeleton elements.
children React.ReactNode - Custom skeleton content.

Loading indicator for virtualized tables.

import {
DataTableVirtualizedBody,
DataTableVirtualizedLoading,
} from "@/components/niko-table/core/data-table-virtualized-structure"
import { Loader2 } from "lucide-react"
<DataTableVirtualizedBody estimateSize={34}>
<DataTableVirtualizedLoading>
<div className="flex items-center justify-center p-8">
<Loader2 className="size-6 animate-spin" />
<span className="ml-2">Loading...</span>
</div>
</DataTableVirtualizedLoading>
</DataTableVirtualizedBody>
Name Type Default Description
children React.ReactNode - Custom loading content.
colSpan number - Number of columns (auto-detected if not provided).
className string - Additional CSS classes.

Virtualized variant of DataTableLoadingMore. Composable “loading more” row for infinite-scroll virtualized tables. Sits outside the virtualizer’s row count (it’s a plain child of TableBody, not a virtual row), so it does not affect estimateSize math.

import {
DataTableVirtualizedBody,
DataTableVirtualizedSkeleton,
DataTableVirtualizedEmptyBody,
DataTableVirtualizedLoadingMore,
} from "@/components/niko-table/core/data-table-virtualized-structure"
<DataTableVirtualizedBody
prefetchThreshold={15}
onNearEnd={() => {
if (hasMore && !isFetching) void loadMore()
}}
>
<DataTableVirtualizedSkeleton rows={15} />
<DataTableVirtualizedEmptyBody />
<DataTableVirtualizedLoadingMore isFetching={isFetching}>
Loading more products...
</DataTableVirtualizedLoadingMore>
</DataTableVirtualizedBody>

See Infinite Scroll Virtualized Table for a complete working example.

Name Type Default Description
isFetching boolean - Required. Whether a next-page fetch is currently in flight. Self-gates on false.
children React.ReactNode "Loading more…" Custom label content (e.g. "Loading more products…").
colSpan number - Number of columns (auto-detected if not provided).
className string - Additional CSS classes on the spinner cell.

Choose the right DnD components based on your use case:

Use Case Components
Row reorder (standard) DataTableDndBody
Column reorder (standard) DataTableDndHeader + DataTableDndColumnBody
Row reorder (virtualized) DataTableVirtualizedDndBody
Column reorder (virtualized) DataTableVirtualizedDndHeader + DataTableVirtualizedDndColumnBody

For standard (non-virtualized) tables with drag-and-drop, use these components from data-table-dnd-structure.tsx. They are drop-in replacements for DataTableHeader / DataTableBody when using row or column DnD.

DnD-aware table body that renders rows as draggable items. Drop-in replacement for DataTableBody when using row drag-and-drop. Must be wrapped in a DataTableRowDndProvider.

import { DataTableDndBody } from "@/components/niko-table/core/data-table-dnd-structure"
import { DataTableRowDndProvider } from "@/components/niko-table/components/data-table-row-dnd"
<DataTableRowDndProvider data={data} onReorder={setData}>
<DataTable>
<DataTableHeader />
<DataTableDndBody />
</DataTable>
</DataTableRowDndProvider>
Name Type Default Description
children React.ReactNode - Child components.
className string - Additional CSS classes.
onRowClick (row: TData) => void - Called when a row is clicked.
getRowMemoKey (row: TData) => string - Per-row key encoding external row state. When it changes for a row, only that row re-renders.

DnD-aware table header that renders column headers as draggable items. Drop-in replacement for DataTableHeader when using column drag-and-drop. Must be wrapped in a DataTableColumnDndProvider.

import { DataTableDndHeader, DataTableDndColumnBody } from "@/components/niko-table/core/data-table-dnd-structure"
import { DataTableColumnDndProvider } from "@/components/niko-table/components/data-table-column-dnd"
<DataTableColumnDndProvider columnOrder={columnOrder} onColumnOrderChange={setColumnOrder}>
<DataTable>
<DataTableDndHeader />
<DataTableDndColumnBody />
</DataTable>
</DataTableColumnDndProvider>
Name Type Default Description
className string - Additional CSS classes.
sticky boolean true Makes the header sticky at the top when scrolling.

DnD-aware table body for column drag-and-drop. Each cell follows its column’s drag position using useSortable. Must be wrapped in a DataTableColumnDndProvider.

Name Type Default Description
children React.ReactNode - Child components.
className string - Additional CSS classes.
onRowClick (row: TData) => void - Called when a row is clicked.
getRowMemoKey (row: TData) => string - Per-row key encoding external row state. When it changes for a row, only that row re-renders.

For combining virtualization with drag-and-drop, use these specialized components from data-table-virtualized-dnd-structure.tsx.

Virtualized DnD-aware table body for row drag-and-drop. Combines @tanstack/react-virtual for windowing with @dnd-kit/sortable for drag interactions. Must be wrapped in a DataTableRowDndProvider.

import {
DataTableVirtualizedHeader,
DataTableVirtualizedDndBody,
DataTableVirtualizedEmptyBody,
} from "@/components/niko-table/core/data-table-virtualized-dnd-structure"
import { DataTableRowDndProvider } from "@/components/niko-table/components/data-table-row-dnd"
<DataTableRowDndProvider data={data} onReorder={setData}>
<DataTable height={500}>
<DataTableVirtualizedHeader />
<DataTableVirtualizedDndBody estimateSize={40} overscan={10}>
<DataTableVirtualizedEmptyBody />
</DataTableVirtualizedDndBody>
</DataTable>
</DataTableRowDndProvider>
Name Type Default Description
children React.ReactNode - Child components (e.g. DataTableVirtualizedEmptyBody).
estimateSize number 34 Estimated row height in pixels.
overscan number 20 Number of items to render outside visible area.
className string - Additional CSS classes.
onScroll (event: ScrollEvent) => void - Scroll event handler.
onScrolledTop () => void - Called when scrolled to top.
onScrolledBottom () => void - Called when scrolled to bottom.
scrollThreshold number 50 Pixel threshold for top/bottom scroll events.
onRowClick (row: TData, event: React.MouseEvent) => void - Called when a row is clicked.
getRowMemoKey (row: TData) => string - Per-row key encoding external row state. When it changes for a row, only that row re-renders.

Virtualized DnD-aware table header for column drag-and-drop. Must be wrapped in a DataTableColumnDndProvider.

import {
DataTableVirtualizedDndHeader,
DataTableVirtualizedDndColumnBody,
} from "@/components/niko-table/core/data-table-virtualized-dnd-structure"
import { DataTableColumnDndProvider } from "@/components/niko-table/components/data-table-column-dnd"
<DataTableColumnDndProvider columnOrder={columnOrder} onColumnOrderChange={setColumnOrder}>
<DataTable height={500}>
<DataTableVirtualizedDndHeader />
<DataTableVirtualizedDndColumnBody estimateSize={40} />
</DataTable>
</DataTableColumnDndProvider>
Name Type Default Description
className string - Additional CSS classes.
sticky boolean true Makes the header sticky at the top when scrolling.

Virtualized DnD-aware table body for column drag-and-drop. Each cell follows column drag position using useSortable. Must be wrapped in a DataTableColumnDndProvider.

Name Type Default Description
children React.ReactNode - Child components (e.g. DataTableVirtualizedEmptyBody).
estimateSize number 34 Estimated row height in pixels.
overscan number 20 Number of items to render outside visible area.
className string - Additional CSS classes.
onScroll (event: ScrollEvent) => void - Scroll event handler.
onScrolledTop () => void - Called when scrolled to top.
onScrolledBottom () => void - Called when scrolled to bottom.
scrollThreshold number 50 Pixel threshold for top/bottom scroll events.
onRowClick (row: TData, event: React.MouseEvent) => void - Called when a row is clicked.