Skip to content
6

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

Usage

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
PropTypeDefault
table*ReactTable<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature:…none
actionBarReact.ReactNodenone

<DataTableToolbar />

+ native element props
PropTypeDefault
table*ReactTable<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature:…none

<DataTableColumnHeader />

+ native element props
PropTypeDefault
column*Column<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature: Tabnone
label*stringnone

<DataTablePagination />

+ native element props
PropTypeDefault
table*ReactTable<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature:…none
pageSizeOptionsnumber[][10, 20, 30, 40, 50]

<DataTableViewOptions />

+ native element props
PropTypeDefault
table*ReactTable<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature:…none
disabledbooleannone

<DataTableFacetedFilter />

PropTypeDefault
columnColumn<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature: Tabnone
titlestringnone
options*Option[]none
multiplebooleannone

<DataTableSliderFilter />

PropTypeDefault
column*Column<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature: Tabnone
titlestringnone

<DataTableDateFilter />

PropTypeDefault
column*Column<{ columnFacetingFeature: TableFeature; columnFilteringFeature: TableFeature; columnOrderingFeature: TableFeature; columnPinningFeature: TableFeature; columnSizingFeature: TableFeature; columnVisibilityFeature: Tabnone
titlestringnone
multiplebooleannone

<DataTableSkeleton />

+ native element props
PropTypeDefault
columnCount*numbernone
rowCountnumber10
filterCountnumber0
cellWidthsstring[]['auto']
withViewOptionsbooleantrue
withPaginationbooleantrue
shrinkZerobooleanfalse

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