Table
Table Examples
Copy-ready examples with live previews.
Basic
Define columns and rows. Use `renderCell` for formatting.
Live preview
| Name | Role | Status | Created | Requests | |
|---|---|---|---|---|---|
| Luis Chavez | luis@cssnt.dev | Admin | Active | 2025-12-15 | 1,420 |
| Andrea M | andrea@cssnt.dev | Editor | Paused | 2025-11-02 | 210 |
| Carlos R | carlos@cssnt.dev | Viewer | Blocked | 2025-10-19 | 19 |
Basic
import Table from "@/packages/ui/src/_components/table/Table";
import type { ITableColumn } from "@/packages/ui/src/_components/table/Table.type";
const rows: UserRow[] = [
{ id: "u_1", name: "Luis", email: "luis@cssnt.dev", role: "Admin", status: "Active", createdAt: "2025-12-15", requests: 1420 },
{ id: "u_2", name: "Andrea", email: "andrea@cssnt.dev", role: "Editor", status: "Paused", createdAt: "2025-11-02", requests: 210 },
];
const columns = [
{ key: "name", header: "Name" },
{ key: "email", header: "Email", mono: true },
{ key: "role", header: "Role" },
{ key: "status", header: "Status" },
{ key: "createdAt", header: "Created", mono: true },
{ key: "requests", header: "Requests", align: "right", mono: true, width: 120 },
];
export default function Example() {
return (
<Table<UserRow>
caption="Team"
columns={columns}
rows={rows}
rowKey={(r) => r.id}
renderCell={(row, col) => (col.key === "requests" ? row.requests.toLocaleString() : row[col.key])}
/>
);
}Striped + dense + column dividers
Quick styling flags for data-heavy views.
Live preview
| Name | Role | Status | Created | Requests | |
|---|---|---|---|---|---|
| Luis Chavez | luis@cssnt.dev | Admin | Active | 2025-12-15 | 1420 |
| Andrea M | andrea@cssnt.dev | Editor | Paused | 2025-11-02 | 210 |
| Carlos R | carlos@cssnt.dev | Viewer | Blocked | 2025-10-19 | 19 |
Dense table
import Table from "@/packages/ui/src/_components/table/Table";
export default function Example({ columns, rows }: any) {
return (
<Table
caption="Striped + dense + colDividers"
variant="primary"
density="dense"
striped
colDividers
columns={columns}
rows={rows}
/>
);
}Sticky header
Wrap the table in a scroll container and enable `stickyHeader`.
Live preview
| Name | Role | Status | Created | Requests | |
|---|---|---|---|---|---|
| Luis Chavez | luis@cssnt.dev | Admin | Active | 2025-12-15 | 1420 |
| Andrea M | andrea@cssnt.dev | Editor | Paused | 2025-11-02 | 210 |
| Carlos R | carlos@cssnt.dev | Viewer | Blocked | 2025-10-19 | 19 |
| Luis Chavez | luis@cssnt.dev | Admin | Active | 2025-12-15 | 1420 |
| Andrea M | andrea@cssnt.dev | Editor | Paused | 2025-11-02 | 210 |
| Carlos R | carlos@cssnt.dev | Viewer | Blocked | 2025-10-19 | 19 |
| Luis Chavez | luis@cssnt.dev | Admin | Active | 2025-12-15 | 1420 |
| Andrea M | andrea@cssnt.dev | Editor | Paused | 2025-11-02 | 210 |
| Carlos R | carlos@cssnt.dev | Viewer | Blocked | 2025-10-19 | 19 |
| Luis Chavez | luis@cssnt.dev | Admin | Active | 2025-12-15 | 1420 |
| Andrea M | andrea@cssnt.dev | Editor | Paused | 2025-11-02 | 210 |
| Carlos R | carlos@cssnt.dev | Viewer | Blocked | 2025-10-19 | 19 |
Sticky header
import Table from "@/packages/ui/src/_components/table/Table";
export default function Example({ columns, rows }: any) {
return (
<div style={{ maxHeight: 220, overflow: "auto" }}>
<Table
caption="Sticky header (scroll)"
stickyHeader
variant="secondary"
columns={columns}
rows={rows}
rowKey={(r: any, i: number) => r.id + "-" + i}
/>
</div>
);
}Tone / inverse
Use `variant` + `tone` for contrast rules in docs and dark surfaces.
Live preview
| Name | Role | Status | Created | Requests | |
|---|---|---|---|---|---|
| Luis Chavez | luis@cssnt.dev | Admin | Active | 2025-12-15 | 1420 |
| Andrea M | andrea@cssnt.dev | Editor | Paused | 2025-11-02 | 210 |
| Carlos R | carlos@cssnt.dev | Viewer | Blocked | 2025-10-19 | 19 |
| Name | Role | Status | Created | Requests | |
|---|---|---|---|---|---|
| Luis Chavez | luis@cssnt.dev | Admin | Active | 2025-12-15 | 1420 |
| Andrea M | andrea@cssnt.dev | Editor | Paused | 2025-11-02 | 210 |
| Carlos R | carlos@cssnt.dev | Viewer | Blocked | 2025-10-19 | 19 |
| Name | Role | Status | Created | Requests | |
|---|---|---|---|---|---|
| Luis Chavez | luis@cssnt.dev | Admin | Active | 2025-12-15 | 1420 |
| Andrea M | andrea@cssnt.dev | Editor | Paused | 2025-11-02 | 210 |
| Carlos R | carlos@cssnt.dev | Viewer | Blocked | 2025-10-19 | 19 |
Tones
import Table from "@/_components/table/Table";
export default function Example({ columns, rows }: any) {
return (
<>
<Table caption="Inverse" variant="neutral-inverse" columns={columns} rows={rows} />
<Table caption="Pure white" tone="pure-white" columns={columns} rows={rows} />
<Table caption="Pure black" tone="pure-black" variant="neutral-inverse" columns={columns} rows={rows} />
</>
);
}Empty state
When `rows` is empty, the table renders a single row with `emptyText`.
Live preview
| Name | Role | Status | Created | Requests | |
|---|---|---|---|---|---|
| No users yet | |||||
Empty state
import Table from "@/packages/ui/src/_components/table/Table";
export default function Example({ columns }: any) {
return <Table caption="Empty table" columns={columns} rows={[]} emptyText="No users yet" />;
}Row selection (controlled)
Use `isRowSelected` to apply the selected row class, and handle selection in your UI.
Live preview
| Name | Role | Status | Created | Requests | |
|---|---|---|---|---|---|
| luis@cssnt.dev | Admin | Active | 2025-12-15 | 1,420 | |
| andrea@cssnt.dev | Editor | Paused | 2025-11-02 | 210 | |
| carlos@cssnt.dev | Viewer | Blocked | 2025-10-19 | 19 |
Selectable rows
"use client";
import { useState } from "react";
import Table from "@/packages/ui/src/_components/table/Table";
export default function Example({ columns, rows }: any) {
const [selectedId, setSelectedId] = useState("u_2");
return (
<Table
caption="Selectable rows (click)"
variant="info"
columns={columns}
rows={rows}
rowKey={(r: any) => r.id}
isRowSelected={(r: any) => r.id === selectedId}
renderCell={(row: any, col: any) => {
if (col.key === "name") {
return (
<button type="button" style={{ all: "unset", cursor: "pointer" }} onClick={() => setSelectedId(row.id)}>
{row.name}
</button>
);
}
return row[col.key];
}}
/>
);
}Actions column
Add an actions column and render buttons or menus per row.
Live preview
| Name | Role | Status | Created | Requests | Actions | |
|---|---|---|---|---|---|---|
| Luis Chavez | luis@cssnt.dev | Admin | Active | 2025-12-15 | 1,420 | |
| Andrea M | andrea@cssnt.dev | Editor | Paused | 2025-11-02 | 210 | |
| Carlos R | carlos@cssnt.dev | Viewer | Blocked | 2025-10-19 | 19 |
Actions
import Table from "@/packages/ui/src/_components/table/Table";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";
export default function Example({ columns, rows }: any) {
return (
<Table
caption="Actions column"
variant="success"
columns={[...columns, { key: "id", header: "Actions", align: "right", width: 220 }]}
rows={rows}
rowKey={(r: any) => r.id}
renderCell={(row: any, col: any) => {
if (col.key !== "id") return row[col.key];
return (
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
<Button variant="transparent" variantRadio="sm" onClick={() => console.log("view", row.id)}>
View
</Button>
<Button variant="primary" variantRadio="sm" onClick={() => console.log("edit", row.id)}>
Edit
</Button>
</div>
);
}}
/>
);
}Custom cell rendering
Format values and render UI per cell.
Live preview
| Name | Role | Status | Created | Requests | |
|---|---|---|---|---|---|
| Luis Chavez | luis@cssnt.dev | Admin | Active | 2025-12-15 | 1,420 |
| Andrea M | andrea@cssnt.dev | Editor | Paused | 2025-11-02 | 210 |
| Carlos R | carlos@cssnt.dev | Viewer | Blocked | 2025-10-19 | 19 |
Custom cells
import Table from "@/packages/ui/src/_components/table/Table";
export default function Example({ columns, rows }: any) {
return (
<Table
caption="Custom cell rendering"
variant="tertiary"
columns={columns}
rows={rows}
renderCell={(row: any, col: any) => {
if (col.key === "email") return <a href={`mailto:${row.email}`}>{row.email}</a>;
if (col.key === "requests") return Number(row.requests).toLocaleString();
return row[col.key];
}}
/>
);
}On this page
Table Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| columns | Array<ITableColumn<T>> | Yes | — | Column definitions (header, key, alignment, etc.). |
| rows | Array<T> | Yes | — | Table data (one object per row). |
| caption | React.ReactNode | No | — | Optional `<caption>` rendered inside the `<table>`. |
| variant | "neutral" | "neutral-inverse" | "primary" | "secondary" | "tertiary" | "success" | "warning" | "danger" | "info" | No | "neutral" | Semantic variant (mapped to modifier classes). |
| tone | "default" | "pure-white" | "pure-black" | No | "default" | Background/contrast tone modifier. |
| density | "default" | "dense" | No | "default" | Spacing density modifier. |
| striped | boolean | No | false | Adds striped row styling. |
| colDividers | boolean | No | false | Adds vertical dividers between columns. |
| stickyHeader | boolean | No | false | Makes the header sticky inside the scroll container. |
| rowKey | (row: T, index: number) => string | number | No | row.id ?? index | Computes the React `key` for each row. |
| isRowSelected | (row: T, index: number) => boolean | No | () => false | Marks a row as selected (adds `is-selected` class). |
| renderCell | (row: T, col: ITableColumn<T>, rowIndex: number) => React.ReactNode | No | row[col.key] | Custom cell renderer. |
| emptyText | React.ReactNode | No | "No data" | Content shown when `rows.length === 0`. |
| className | string | No | — | Extra class name for the root container. |
| customStyles | React.CSSProperties | No | — | Inline styles for the root container. |
| ITableColumn.key | keyof T & string | Yes | — | Property key used to read values from the row object. |
| ITableColumn.header | React.ReactNode | Yes | — | Header content for the column. |
| ITableColumn.align | "left" | "center" | "right" | No | "left" | Horizontal alignment for header + cells. |
| ITableColumn.mono | boolean | No | false | Applies a monospace style class to header + cells for that column. |
| ITableColumn.width | number | string | No | — | Inline width for header/cells (e.g. 160, "12rem", "20%"). |
Components