For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to documentation URLs. Homepage markdown is available at /llms.md/content.md. An agent skill is available at /.well-known/agent-skills/site-skill.md.

Data Table

A sortable data table composed with TanStack Table.

Pass a TanStack table instance to DataTable to render its headers and rows. Manage sorting, filtering, selection, and pagination in the caller.

Installation

$ pnpm dlx shadcn@latest add @cobalt/data-table

Source

"use client";

import { RiArrowDownSFill } from "@remixicon/react";
import { flexRender } from "@tanstack/react-table";
import type { Table as TanStackTable } from "@tanstack/react-table";
import { cn } from "cn";
import * as React from "react";

import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";

/** Per-column class names, read from `columnDef.meta.className`. */
const metaClassName = (meta: unknown) =>
  (meta as { className?: string } | undefined)?.className;

/** Render a TanStack table while leaving data and state under the caller's control. */
function DataTable<TData>({
  table,
  size = "default",
  variant = "default",
  emptyMessage = "No results.",
  className,
  ...props
}: Omit<React.ComponentProps<"table">, "children"> & {
  table: TanStackTable<TData>;
  size?: "default" | "sm";
  /** `ghost` keeps selected rows unfilled and gives the header row more room. */
  variant?: "default" | "ghost";
  emptyMessage?: React.ReactNode;
}) {
  return (
    <Table
      size={size}
      className={cn(
        variant === "ghost" &&
          "[&_th]:py-3 [&_tr[data-state=selected]]:bg-transparent",
        className
      )}
      {...props}
    >
      <TableHeader>
        {table.getHeaderGroups().map((group) => (
          <TableRow key={group.id}>
            {group.headers.map((header) => {
              const sorted = header.column.getIsSorted();
              return (
                <TableHead
                  key={header.id}
                  colSpan={header.colSpan}
                  className={metaClassName(header.column.columnDef.meta)}
                  aria-sort={
                    sorted
                      ? sorted === "asc"
                        ? "ascending"
                        : "descending"
                      : undefined
                  }
                >
                  {header.isPlaceholder ? null : header.column.getCanSort() ? (
                    <button
                      type="button"
                      className="flex cursor-pointer items-center gap-0.5 outline-none focus-visible:ring-2 focus-visible:ring-ring"
                      onClick={header.column.getToggleSortingHandler()}
                    >
                      {flexRender(
                        header.column.columnDef.header,
                        header.getContext()
                      )}
                      <RiArrowDownSFill
                        aria-hidden="true"
                        className={cn(
                          "size-4 shrink-0 transition-[transform,color] duration-150 motion-reduce:transition-none",
                          sorted === "asc" && "rotate-180",
                          sorted && "text-foreground"
                        )}
                      />
                    </button>
                  ) : (
                    flexRender(
                      header.column.columnDef.header,
                      header.getContext()
                    )
                  )}
                </TableHead>
              );
            })}
          </TableRow>
        ))}
      </TableHeader>
      <TableBody>
        {table.getRowModel().rows.length ? (
          table.getRowModel().rows.map((row) => (
            <TableRow
              key={row.id}
              data-state={row.getIsSelected() ? "selected" : undefined}
            >
              {row.getVisibleCells().map((cell) => (
                <TableCell
                  key={cell.id}
                  className={metaClassName(cell.column.columnDef.meta)}
                >
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </TableCell>
              ))}
            </TableRow>
          ))
        ) : (
          <TableRow>
            <TableCell
              colSpan={Math.max(1, table.getVisibleLeafColumns().length)}
              className="h-40 text-center text-muted-foreground"
            >
              {emptyMessage}
            </TableCell>
          </TableRow>
        )}
      </TableBody>
    </Table>
  );
}

export { DataTable };