Overview
Table follows a compound-component API so you can compose headers, body rows, and cells declaratively while keeping visual styles and interactions consistent with the design system.
Basic Usage
- Code
- Preview
import { Table } from '@peppermint-design/devreadykit-custom';
type Invoice = {
id: string;
label: string;
due: string;
amount: number;
};
const invoices: Invoice[] = [
{ id: "inv-101", label: "Brand identity", due: "Jan 18, 2025", amount: 3800 },
{ id: "inv-102", label: "Website redesign", due: "Jan 22, 2025", amount: 5600 },
];
export default function TableBasic() {
return (
<Table caption="Client invoices">
<Table.Header>
<Table.Head id="invoice" label="Invoice" isRowHeader />
<Table.Head id="due" label="Due date" />
<Table.Head id="amount" label="Amount" align="right" />
</Table.Header>
<Table.Body items={invoices}>
{(invoice) => (
<Table.Row id={invoice.id}>
<Table.Cell>
<span className="font-semibold text-neutral-10">{invoice.label}</span>
</Table.Cell>
<Table.Cell>{invoice.due}</Table.Cell>
<Table.Cell align="right">${invoice.amount.toLocaleString()}</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table>
);
}
Selection & Actions
Enable checkboxes by settingselectionMode. Rows register automatically and the header renders a “select all” checkbox for multiple selection.
- Code
- Preview
import { useState } from "react";
import { Table, Badge } from "@peppermint-design/devreadykit-custom";
import { Overflow } from "@/assets/icons/overflow";
type Member = {
id: string;
name: string;
role: string;
status: "active" | "inactive";
};
const members: Member[] = [
{ id: "ava", name: "Ava Reynolds", role: "Designer", status: "active" },
{ id: "miles", name: "Miles Chen", role: "Engineering Manager", status: "inactive" },
];
export default function TableWithSelection() {
const [selected, setSelected] = useState<React.Key[]>([]);
return (
<Table
selectionMode="multiple"
selectedKeys={selected}
onSelectionChange={setSelected}
emptyState="No team members yet"
>
<Table.Header>
<Table.Head id="name" label="Name" isRowHeader />
<Table.Head id="status" label="Status" />
<Table.Head id="role" label="Role" />
</Table.Header>
<Table.Body items={members}>
{(member) => (
<Table.Row id={member.id}>
<Table.Cell>{member.name}</Table.Cell>
<Table.Cell>
<Badge variant="status" color={member.status === "active" ? "available" : "offline"} />
<span className="ml-200 text-300 text-neutral-40 capitalize">{member.status}</span>
</Table.Cell>
<Table.Cell>{member.role}</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table>
);
}
Sorting
Pass a controlledsortDescriptor to drive column sorting. Clicking any header with allowsSorting toggles between ascending and descending order.
- Code
- Preview
import { useMemo, useState } from "react";
import { Table, type TableSortDescriptor } from "@peppermint-design/devreadykit-custom";
type Customer = {
id: string;
name: string;
total: number;
};
const customers: Customer[] = [
{ id: "1", name: "Andrew Martin", total: 760 },
{ id: "2", name: "Jessica Taylor", total: 540 },
];
export default function SortableTable() {
const [sortDescriptor, setSortDescriptor] = useState<TableSortDescriptor>({
column: "name",
direction: "ascending",
});
const sortedCustomers = useMemo(() => {
const items = [...customers];
const multiplier = sortDescriptor.direction === "ascending" ? 1 : -1;
return items.sort((a, b) =>
sortDescriptor.column === "total"
? (a.total - b.total) * multiplier
: a.name.localeCompare(b.name) * multiplier,
);
}, [sortDescriptor]);
return (
<Table sortDescriptor={sortDescriptor} onSortChange={setSortDescriptor}>
<Table.Header>
<Table.Head id="name" label="Customer" allowsSorting isRowHeader />
<Table.Head id="total" label="Lifetime value" allowsSorting align="right" />
</Table.Header>
<Table.Body items={sortedCustomers}>
{(customer) => (
<Table.Row id={customer.id}>
<Table.Cell>{customer.name}</Table.Cell>
<Table.Cell align="right">${customer.total.toLocaleString()}</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table>
);
}
Rich Cells
Compose cells with any JSX — avatars, badges, or multi-line content — while keeping spacing and typography consistent.- Code
- Preview
import { Table, Avatar } from '@peppermint-design/devreadykit-custom';
<Table.Cell>
<div className="flex items-center gap-300">
<Avatar size={40} name={member.name} />
<div>
<div className="text-350 font-semibold text-neutral-10">{member.name}</div>
<div className="text-300 text-neutral-40">{member.email}</div>
</div>
</div>
</Table.Cell>
Props
Table.Head
| Prop | Type | Default | Description |
|---|---|---|---|
| id | string | – | Unique identifier for the column. |
| label | React.ReactNode | – | Header label. If omitted, children render instead. |
| allowsSorting | boolean | false | Enables sort toggling on click. |
| align | "left" | "center" | "right" | "left" | Text alignment for the entire column. |
| tooltip | string | – | Optional title attribute for the header. |
| isRowHeader | boolean | false | Marks the column as a row header, rendering body cells as <th scope="row">. |
Table.Body
| Prop | Type | Default | Description |
|---|---|---|---|
| items | T[] | – | Data items to render. |
| children | React.ReactNode | (item: T, index: number) => React.ReactElement<Table.Row> | – | Either a render function or static rows. |
Table.Row
| Prop | Type | Default | Description |
|---|---|---|---|
| id | React.Key | – | Row identifier used for selection. |
| isDisabled | boolean | false | Disables selection and hover styles for the row. |
Table.Cell
| Prop | Type | Default | Description |
|---|---|---|---|
| align | "left" | "center" | "right" | "left" | Overrides alignment for the cell. |
| isRowHeader | boolean | false | Forces the cell to render as <th scope="row">. |
Types
type TableSelectionMode = "none" | "single" | "multiple";
type TableSortDirection = "ascending" | "descending";
type TableSortDescriptor = {
column: string;
direction: TableSortDirection;
};