Command Palette

Search for a command to run...

Data Table

A full-featured data table built on TanStack Table: sticky column pinning, row virtualization for large datasets, dnd-kit drag-to-reorder rows, a paginated footer, a filter toolbar with text / number / range (slider) / date / faceted (select) filters driven by per-column meta, a column-visibility view menu, sortable column headers, and a loading skeleton. Bring your own TanStack table instance; the pieces are composable.

A full-featured data table built on TanStack Table. You own the table instance (columns, models, state); ikui gives you the composable UI around it: a scroll container with sticky column pinning, opt-in row virtualization for large datasets, dnd-kit drag-to-reorder rows, a paginated footer, and a filter toolbar driven by per-column meta.

The toolbar reads each column's meta.variant and renders the matching control — text, number, range (a slider popover), date / dateRange (a calendar popover), and select / multiSelect (a faceted command popover) — alongside a column-visibility view menu. Sortable headers, a loading skeleton, and a row drag handle ship as separate pieces you compose as needed.

Installation

One-time setup: add the @ikui registry to your components.json.

components.json
{
  "registries": {
    "@ikui": "https://ik-ui.pages.dev/r/{name}.json"
  }
}

Then install the component:

pnpm dlx shadcn@latest add @ikui/data-table

Usage

Declare filters through columnDef.meta, then hand the table to DataTable with DataTableToolbar as its child.

import {
  createPaginatedRowModel,
  tableFeatures,
  useTable,
} from "@tanstack/react-table";
import { DataTable } from "@/components/data-table";
import { DataTableToolbar } from "@/components/data-table-toolbar";
import { DataTableColumnHeader } from "@/components/data-table-column-header";
import { dataTableFeatures } from "@/lib/data-table-utils";

const columns = [
  {
    accessorKey: "title",
    header: ({ column }) => (
      <DataTableColumnHeader column={column} title="Title" />
    ),
    meta: { label: "Title", variant: "text", placeholder: "Filter titles..." },
    enableColumnFilter: true,
    filterFn: "includesString",
  },
  {
    accessorKey: "status",
    header: "Status",
    meta: {
      label: "Status",
      variant: "select",
      options: [
        { label: "Todo", value: "todo" },
        { label: "Done", value: "done" },
      ],
    },
    enableColumnFilter: true,
    filterFn: "arrIncludesSome",
  },
];

function Example() {
  const table = useTable({
    data,
    columns,
    features: tableFeatures({
      ...dataTableFeatures,
      paginatedRowModel: createPaginatedRowModel(),
    }),
  });

  return (
    <DataTable table={table}>
      <DataTableToolbar table={table} />
    </DataTable>
  );
}

Examples

Large datasets (virtualized)

Row virtualization only kicks in when the final row model holds more than 20 rows, so it and pagination are mutually exclusive: omit paginatedRowModel (or set showPagination={false}) to let the full dataset reach the table, and give the container a bounded height so it becomes the scroll element the virtualizer measures against. The example below renders 5,000 rows while only mounting the handful in view.

Virtualization trims the DOM, not memory or compute — sorting and filtering still run over every row on the client. Past tens of thousands of rows, move to server-side pagination or infinite loading instead.

Row selection

Add a select column that renders a Checkbox bound to the row's selection state, and a header checkbox for select-all. Pass an actionBar to DataTable — it renders below the footer whenever rows are selected.

Drag to reorder

Set enableDragAndDrop, give the table a stable getRowId, and add a column with id "drag" rendering DataTableDragHandle. Reorder your own data in onDragEnd with dnd-kit's arrayMove.

Column pinning

Pin columns with initialState.columnPinning. Pinned columns stay put with an inset shadow while the rest of a wide table scrolls horizontally.

Loading skeleton

Render DataTableSkeleton while data is loading. Match columnCount to your columns; rowCount, filterCount, and the pagination/view toggles are configurable.

Column meta

The toolbar and filters read these fields off columnDef.meta (types are added by data-table-utils.ts via module augmentation):

PropTypeDefault
label
string
-
placeholder
string
-
variant
"text" | "number" | "range" | "date" | "dateRange" | "select" | "multiSelect"
-
options
{ label; value; count?; icon? }[]
-
range
[number, number]
-
unit
string
-

Props

PropTypeDefault
table
Table<TData>
-
enableVirtualization
boolean
true
estimateRowSize
number
80
enableDragAndDrop
boolean
false
onDragEnd
(event: DragEndEvent) => void
-
getRowId
(row: TData) => number | string
-
showPagination
boolean
true
totalCount
number
-
actionBar
React.ReactNode
-
footer
React.ReactNode
-
emptyState
React.ReactNode
-