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.
Install
npx react-principles add tableTheme 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.
| Name | Role | Status | |
|---|---|---|---|
| Alice Johnson | alice@example.com | admin | active |
| Bob Smith | bob@example.com | editor | active |
| Carol Williams | carol@example.com | viewer | active |
| David Brown | david@example.com | editor | inactive |
| Eva Martinez | eva@example.com | admin | active |
| Name | Role | Status | |
|---|---|---|---|
| Alice Johnson | alice@example.com | admin | active |
| Bob Smith | bob@example.com | editor | active |
| Carol Williams | carol@example.com | viewer | active |
| David Brown | david@example.com | editor | inactive |
| Eva Martinez | eva@example.com | admin | active |
Styled Primitives
The Table component provides styled sub-components that render correct HTML elements. Each extends native HTML attributes for full flexibility.
| Invoice | Status | Method | Amount |
|---|---|---|---|
| INV001 | Paid | Credit Card | $250.00 |
| INV002 | Pending | Bank Transfer | $150.00 |
| INV003 | Unpaid | PayPal | $350.00 |
| Total | $750.00 | ||
TanStack Table Integration
Explore all variants and interactive states in Storybook.
Open Storybookopen_in_newFully interactive — try sorting columns, filtering rows, and navigating pages. The table responds to the current app theme automatically.
Name | Email | Role | Status | Created |
|---|---|---|---|---|
| Alice Johnson | alice@example.com | admin | active | Jan 15, 2024 |
| Bob Smith | bob@example.com | editor | active | Feb 10, 2024 |
| Carol Williams | carol@example.com | viewer | active | Feb 20, 2024 |
| David Brown | david@example.com | editor | inactive | Mar 5, 2024 |
| Eva Martinez | eva@example.com | admin | active | Mar 12, 2024 |
| Frank Garcia | frank@example.com | viewer | active | Mar 18, 2024 |
| Grace Lee | grace@example.com | editor | active | Apr 1, 2024 |
| Henry Wilson | henry@example.com | viewer | inactive | Apr 10, 2024 |
Page 1 of 3 (20 rows)
Column Config
Define columns with ColumnDef<T>. Wrap in useMemo to prevent re-instantiation on every render.
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> ); }, }, ], []);
Code Snippet
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>
Copy-Paste (Single File)
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> ); }
Props
Each sub-component extends its corresponding native HTML element attributes.
| Component | Type | Description |
|---|---|---|
Table | HTMLAttributes<HTMLDivElement> | Root wrapper with overflow-x-auto and border. Accepts className. |
Table.Header | HTMLAttributes<HTMLTableSectionElement> | Renders <thead> with border-b and background styling. |
Table.Body | HTMLAttributes<HTMLTableSectionElement> | Renders <tbody> with divide-y row separators. |
Table.Footer | HTMLAttributes<HTMLTableSectionElement> | Renders <tfoot> with border-t and background styling. |
Table.Row | HTMLAttributes<HTMLTableRowElement> | Renders <tr> with hover state transition. |
Table.Head | ThHTMLAttributes<HTMLTableCellElement> | Renders <th> with uppercase tracking-wide styling. |
Table.Cell | TdHTMLAttributes<HTMLTableCellElement> | Renders <td> with default padding. |
Table.Caption | HTMLAttributes<HTMLTableCaptionElement> | Renders <caption> positioned at top. |
UserTable (TanStack Table)
| Prop | Type | Required | Description |
|---|---|---|---|
data | T[] | required | Array of row data to display in the table. |
columns | ColumnDef<T>[] | required | Column definitions including header, accessor, and optional cell renderer. |
pageSize | number | optional | Number of rows shown per page. Defaults to 8. |
globalFilter | string | optional | Text filter applied across all column values simultaneously. |
sorting | SortingState | optional | Controlled sorting state. Pair with onSortingChange for external control. |