Skip to content

DataTable

Generic table component for admin and list views. Handles sortable columns, row actions, pagination, and selection without re-implementing table UI per entity.


Quick Start

import { DataTable } from "@/components/ui/DataTable";
import { Pencil, Trash2 } from "lucide-react";

const headers: DataTableHeader<Product>[] = [
  { key: "name", label: "Name" },
  { key: "status", label: "Status", align: "center", width: "120px" },
];

const cells: DataTableCell<Product>[] = [
  { content: (row) => row.name },
  {
    align: "center",
    dataCell: (row) => <StatusBadge status={row.status} />,
  },
];

<DataTable
  data={products}
  headers={headers}
  cells={cells}
  onRowClick={(e, row) => router.push(`/products/${row.id}`)}
/>

Note

DataTable requires each data item to have an id: string field.


DataTableHeader<T>

Defines a column header:

Field Type Description
key string Unique column key
label string Display text
align "left" \| "center" \| "right" Header text alignment
width string CSS width (e.g. "120px", "20%")
sortIcon ReactNode Sort direction indicator
onClick () => void Called when header is clicked (for sort)
onCheckedChange (checked: boolean) => void Select-all checkbox callback
checked boolean Select-all checkbox state
headerCell () => ReactNode Fully custom header cell renderer

DataTableCell<T>

Defines a data cell in the same column position:

Field Type Description
content string \| ((row: T) => ReactNode) Cell content (string or renderer)
align "left" \| "center" \| "right" Cell content alignment
className string Additional cell class
dataCell (row: T) => ReactNode Fully custom cell renderer (overrides content)
onCheckedChange (row: T, checked: boolean) => void Per-row checkbox callback
checked (row: T) => boolean Per-row checkbox state
actions DataTableActionsConfig<T> Inline actions dropdown (see below)

Row Actions

Pass an actions config on the last cell to render a dropdown:

const cells: DataTableCell<Product>[] = [
  { content: (row) => row.name },
  {
    actions: {
      onEdit: (row) => router.push(`/products/${row.id}`),
      onDelete: (row) => deleteProduct(row.id),
      customActions: [
        {
          key: "archive",
          label: "Archive",
          icon: Archive,
          onClick: (row) => archiveProduct(row.id),
        },
      ],
    },
  },
];

DataTableActionsConfig<T>

Field Type Description
onEdit (row: T) => void Wires the built-in "Edit" action
onDelete (row: T) => void Wires the built-in "Delete" action
customActions DataTableAction<T>[] Additional actions (see below)
triggerClassName string Class on the dropdown trigger
menuWidth string Dropdown menu width
hideCopy boolean Hide the built-in "Copy ID" action

DataTableAction<T>

Field Type Description
key string Unique key
label string Display label
icon LucideIcon Item icon
onClick (row: T) => void Click handler
getLabel (row: T) => string Dynamic label
getIcon (row: T) => LucideIcon Dynamic icon
visible (row: T) => boolean Conditionally show
disabled (row: T) => boolean Conditionally disable
variant "default" \| "destructive" Destructive items render in red

DataTable Component Props

Prop Type Description
data T[] Row data (items must have id: string)
headers DataTableHeader<T>[] Column header definitions
cells DataTableCell<T>[] Cell definitions (parallel array to headers)
onRowClick (e: MouseEvent, row: T) => void Row click handler
gridTemplateColumns string CSS grid template columns (auto-generated if omitted)
scrollable boolean Sticky header + scrollable body
pinnedData T[] Rows pinned to the top (visually distinct)
firstCellPadding boolean Extra left padding on the first cell

DataTablePagination

Standalone pagination controls. Wire to any list hook:

import { DataTablePagination } from "@/components/ui/DataTable";

<DataTablePagination
  currentPage={currentPage}
  totalPages={totalPages}
  pageSize={pageSize}
  totalCount={totalCount}
  pageCount={totalPages}
  onPageChange={setCurrentPage}
  onPageSizeChange={setPageSize}
/>

DataTableSkeleton

Loading placeholder that matches the table layout:

import { DataTableSkeleton } from "@/components/ui/DataTable";

if (isLoading) return <DataTableSkeleton />;

Pinned Rows

Pass items in pinnedData to pin them at the top of the table with a visual highlight. Useful for "recently created" items:

<DataTable
  data={subjects}
  pinnedData={recentlyCopied ? [recentlyCopied] : []}
  headers={headers}
  cells={cells}
/>

Source Files

File Contents
src/components/ui/DataTable/DataTable.tsx Main table component
src/components/ui/DataTable/types.ts DataTableHeader, DataTableCell, DataTableAction, DataTableActionsConfig
src/components/ui/DataTable/DataTablePagination.tsx Pagination controls
src/components/ui/DataTable/DataTableSkeleton.tsx Loading skeleton
src/components/ui/DataTable/DataTableActionsDropdown.tsx Row actions dropdown
src/components/ui/DataTable/index.ts Public exports