Table

Styled HTML table primitives for building data tables. Includes responsive overflow, row hover states, and dark mode support. Use as a foundation for TanStack Table integrations.

AccessibleDark ModeResponsiveStyled PrimitivesTanStack Table v8

Install

$npx react-principles add table
01

Theme Preview

The table adapts seamlessly to both themes via Tailwind's class-based dark mode. Each variant below is rendered with forced styling — independent of the current app theme — so you can compare them side by side.

Lightbg-white · border-slate-200
NameEmailRoleStatus
Alice Johnsonalice@example.comadminactive
Bob Smithbob@example.comeditoractive
Carol Williamscarol@example.comvieweractive
David Browndavid@example.comeditorinactive
Eva Martinezeva@example.comadminactive
Darkbg-[#0d1117] · border-[#1f2937]
NameEmailRoleStatus
Alice Johnsonalice@example.comadminactive
Bob Smithbob@example.comeditoractive
Carol Williamscarol@example.comvieweractive
David Browndavid@example.comeditorinactive
Eva Martinezeva@example.comadminactive
02

Styled Primitives

The Table component provides styled sub-components that render correct HTML elements. Each extends native HTML attributes for full flexibility.

Invoice list
InvoiceStatusMethodAmount
INV001PaidCredit Card$250.00
INV002PendingBank Transfer$150.00
INV003UnpaidPayPal$350.00
Total$750.00
03

TanStack Table Integration

Explore all variants and interactive states in Storybook.

Open Storybookopen_in_new

Fully interactive — try sorting columns, filtering rows, and navigating pages. The table responds to the current app theme automatically.

Name
Email
Role
Status
Created
Alice Johnsonalice@example.comadminactiveJan 15, 2024
Bob Smithbob@example.comeditoractiveFeb 10, 2024
Carol Williamscarol@example.comvieweractiveFeb 20, 2024
David Browndavid@example.comeditorinactiveMar 5, 2024
Eva Martinezeva@example.comadminactiveMar 12, 2024
Frank Garciafrank@example.comvieweractiveMar 18, 2024
Grace Leegrace@example.comeditoractiveApr 1, 2024
Henry Wilsonhenry@example.comviewerinactiveApr 10, 2024

Page 1 of 3 (20 rows)

04

Column Config

Define columns with ColumnDef<T>. Wrap in useMemo to prevent re-instantiation on every render.

components/UserTable.tsx
const columns = useMemo<ColumnDef<User>[]>(() => [
  {
    accessorKey: "name",
    header: "Name",
    cell: (info) => (
      <span className="font-medium">{info.getValue<string>()}</span>
    ),
  },
  {
    accessorKey: "role",
    header: "Role",
    cell: (info) => (
      <span className="rounded-full bg-slate-100 px-2.5 py-0.5 text-xs
        font-medium capitalize dark:bg-slate-800">
        {info.getValue<string>()}
      </span>
    ),
  },
  {
    accessorKey: "status",
    header: "Status",
    cell: (info) => {
      const status = info.getValue<string>();
      return (
        <span className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${
          status === "active"
            ? "bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-400"
            : "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-400"
        }`}>
          {status}
        </span>
      );
    },
  },
], []);
05

Code Snippet

src/ui/Table.tsx
import { Table } from "@/ui/Table";

<Table>
  <Table.Caption>Invoice list</Table.Caption>
  <Table.Header>
    <Table.Row>
      <Table.Head>Invoice</Table.Head>
      <Table.Head>Status</Table.Head>
      <Table.Head>Method</Table.Head>
      <Table.Head className="text-right">Amount</Table.Head>
    </Table.Row>
  </Table.Header>
  <Table.Body>
    <Table.Row>
      <Table.Cell className="font-medium">INV001</Table.Cell>
      <Table.Cell>Paid</Table.Cell>
      <Table.Cell>Credit Card</Table.Cell>
      <Table.Cell className="text-right">$250.00</Table.Cell>
    </Table.Row>
  </Table.Body>
  <Table.Footer>
    <Table.Row>
      <Table.Cell colSpan={3}>Total</Table.Cell>
      <Table.Cell className="text-right">$250.00</Table.Cell>
    </Table.Row>
  </Table.Footer>
</Table>
06

Copy-Paste (Single File)

Table.tsx
import {
  type HTMLAttributes,
  type ReactNode,
} from "react";
import { cn } from "@/lib/utils";

// ─── Types ────────────────────────────────────────────────────────────────────

export interface TableProps extends HTMLAttributes<HTMLDivElement> {
  children: ReactNode;
}

export interface TableHeaderProps extends HTMLAttributes<HTMLTableSectionElement> {
  children: ReactNode;
}

export interface TableBodyProps extends HTMLAttributes<HTMLTableSectionElement> {
  children: ReactNode;
}

export interface TableFooterProps extends HTMLAttributes<HTMLTableSectionElement> {
  children: ReactNode;
}

export interface TableRowProps extends HTMLAttributes<HTMLTableRowElement> {
  children: ReactNode;
}

export interface TableHeadProps {
  children?: ReactNode;
  className?: string;
}

export interface TableCellProps {
  children?: ReactNode;
  className?: string;
}

export interface TableCaptionProps extends HTMLAttributes<HTMLTableCaptionElement> {
  children?: ReactNode;
}

// ─── Table ───────────────────────────────────────────────────────────────────

export function Table({ className, children, ...props }: TableProps) {
  return (
    <div
      className={cn(
        "w-full overflow-x-auto rounded-xl border border-slate-200 dark:border-[#1f2937]",
        className,
      )}
      {...props}
    >
      <table className="w-full caption-top text-left text-sm">
        {children}
      </table>
    </div>
  );
}

Table.Header = function TableHeader({ className, children, ...props }: TableHeaderProps) {
  return (
    <thead
      className={cn(
        "border-b border-slate-200 bg-slate-50 dark:border-[#1f2937] dark:bg-[#161b22]",
        className,
      )}
      {...props}
    >
      {children}
    </thead>
  );
};

Table.Body = function TableBody({ className, children, ...props }: TableBodyProps) {
  return (
    <tbody
      className={cn(
        "divide-y divide-slate-100 dark:divide-[#1f2937]",
        className,
      )}
      {...props}
    >
      {children}
    </tbody>
  );
};

Table.Footer = function TableFooter({ className, children, ...props }: TableFooterProps) {
  return (
    <tfoot
      className={cn(
        "border-t border-slate-200 bg-slate-50 font-medium dark:border-[#1f2937] dark:bg-[#161b22] dark:text-slate-300",
        className,
      )}
      {...props}
    >
      {children}
    </tfoot>
  );
};

Table.Row = function TableRow({ className, children, ...props }: TableRowProps) {
  return (
    <tr
      className={cn(
        "transition-colors border-b border-transparent last:border-b-0",
        "hover:bg-slate-50 dark:hover:bg-slate-800/50",
        className,
      )}
      {...props}
    >
      {children}
    </tr>
  );
};

Table.Head = function TableHead({ className, children, ...props }: TableHeadProps) {
  return (
    <th
      className={cn(
        "px-4 py-3 text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400",
        className,
      )}
      {...props}
    >
      {children}
    </th>
  );
};

Table.Cell = function TableCell({ className, children, ...props }: TableCellProps) {
  return (
    <td className={cn("px-4 py-3", className)} {...props}>
      {children}
    </td>
  );
};

Table.Caption = function TableCaption({ className, children, ...props }: TableCaptionProps) {
  return (
    <caption className={cn("px-4 py-3 text-sm text-slate-500 dark:text-slate-400", className)} {...props}>
      {children}
    </caption>
  );
}
07

Props

Each sub-component extends its corresponding native HTML element attributes.

ComponentTypeDescription
TableHTMLAttributes<HTMLDivElement>Root wrapper with overflow-x-auto and border. Accepts className.
Table.HeaderHTMLAttributes<HTMLTableSectionElement>Renders <thead> with border-b and background styling.
Table.BodyHTMLAttributes<HTMLTableSectionElement>Renders <tbody> with divide-y row separators.
Table.FooterHTMLAttributes<HTMLTableSectionElement>Renders <tfoot> with border-t and background styling.
Table.RowHTMLAttributes<HTMLTableRowElement>Renders <tr> with hover state transition.
Table.HeadThHTMLAttributes<HTMLTableCellElement>Renders <th> with uppercase tracking-wide styling.
Table.CellTdHTMLAttributes<HTMLTableCellElement>Renders <td> with default padding.
Table.CaptionHTMLAttributes<HTMLTableCaptionElement>Renders <caption> positioned at top.

UserTable (TanStack Table)

PropTypeRequiredDescription
dataT[]requiredArray of row data to display in the table.
columnsColumnDef<T>[]requiredColumn definitions including header, accessor, and optional cell renderer.
pageSizenumberoptionalNumber of rows shown per page. Defaults to 8.
globalFilterstringoptionalText filter applied across all column values simultaneously.
sortingSortingStateoptionalControlled sorting state. Pair with onSortingChange for external control.
React Principles