Data Table
TanStack-powered data table: faceted, text, range and date filters, sortable columns, column visibility, row selection and pagination, with page, sort and filters kept in the URL. One useDataTable hook drives it: pass pageCount to query server-side, or omit it to sort, filter and page a local array in memory. Ships as a folder of composable parts.
Example
Installation
npx shadcn@latest add https://hirael.com/r/data-table.jsonUsage
import { DataTable } from "@/components/data-table/data-table"
import { DataTableToolbar } from "@/components/data-table/data-table-toolbar"
import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header"
import { DataTablePagination } from "@/components/data-table/data-table-pagination"
import { DataTableViewOptions } from "@/components/data-table/data-table-view-options"
import { DataTableFacetedFilter } from "@/components/data-table/data-table-faceted-filter"
import { DataTableSliderFilter } from "@/components/data-table/data-table-slider-filter"
import { DataTableDateFilter } from "@/components/data-table/data-table-date-filter"
import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
import { useDataTable } from "@/components/data-table/use-data-table"Paths assume the default @/ alias from your components.json. The example above shows the parts composed.
API
<DataTable />
+ native element props| Prop | Type | Default |
|---|---|---|
table* | ReactTable<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature:… | none |
actionBar | React.ReactNode | none |
<DataTableToolbar />
+ native element props| Prop | Type | Default |
|---|---|---|
table* | ReactTable<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature:… | none |
<DataTableColumnHeader />
+ native element props| Prop | Type | Default |
|---|---|---|
column* | Column<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature: Tab… | none |
label* | string | none |
<DataTablePagination />
+ native element props| Prop | Type | Default |
|---|---|---|
table* | ReactTable<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature:… | none |
pageSizeOptions | number[] | [10, 20, 30, 40, 50] |
<DataTableViewOptions />
+ native element props| Prop | Type | Default |
|---|---|---|
table* | ReactTable<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature:… | none |
disabled | boolean | none |
<DataTableFacetedFilter />
| Prop | Type | Default |
|---|---|---|
column | Column<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature: Tab… | none |
title | string | none |
options* | Option[] | none |
multiple | boolean | none |
<DataTableSliderFilter />
| Prop | Type | Default |
|---|---|---|
column* | Column<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature: Tab… | none |
title | string | none |
<DataTableDateFilter />
| Prop | Type | Default |
|---|---|---|
column* | Column<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature: Tab… | none |
title | string | none |
multiple | boolean | none |
<DataTableSkeleton />
+ native element props| Prop | Type | Default |
|---|---|---|
columnCount* | number | none |
rowCount | number | 10 |
filterCount | number | 0 |
cellWidths | string[] | ['auto'] |
withViewOptions | boolean | true |
withPagination | boolean | true |
shrinkZero | boolean | false |
Component source
components/data-table/data-table.tsx
'use no memo';
'use client';
import { DataTablePagination } from './data-table-pagination';
import type { DataTableFeatures } from './data-table-features';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/registry/hirael/bases/radix/ui/table';
import { getColumnPinningStyle } from './data-table-utils';
import { cn } from '@/lib/utils';
import type { ReactTable, RowData } from '@tanstack/react-table';
import type * as React from 'react';
interface DataTableProps<TData extends RowData> extends React.ComponentProps<'div'> {
table: ReactTable<DataTableFeatures, TData>;
actionBar?: React.ReactNode;
}
export const DataTable = <TData extends RowData>({
table,
actionBar,
children,
className,
...props
}: DataTableProps<TData>) => {
return (
<div data-slot="data-table" className={cn('flex w-full flex-col gap-2.5 overflow-auto', className)} {...props}>
{children}
<div data-slot="data-table-content" className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead
key={header.id}
colSpan={header.colSpan}
style={{
...getColumnPinningStyle({ column: header.column }),
}}
>
{header.isPlaceholder ? null : <table.FlexRender header={header} />}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
style={{
...getColumnPinningStyle({ column: cell.column }),
}}
>
<table.FlexRender cell={cell} />
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={table.getAllColumns().length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex flex-col gap-2.5">
<DataTablePagination table={table} />
{actionBar && table.getFilteredSelectedRowModel().rows.length > 0 && actionBar}
</div>
</div>
);
};
Dependencies
shadcn registry
badgebuttoncalendarcommanddropdown-menufieldinputpopoverselectseparatorskeletonslidertable
npm
@tanstack/react-tablelucide-reactnuqsreact-day-pickerzod