Skip to main content

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

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 setting selectionMode. Rows register automatically and the header renders a “select all” checkbox for multiple selection.
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 controlled sortDescriptor to drive column sorting. Clicking any header with allowsSorting toggles between ascending and descending order.
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.
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

PropTypeDefaultDescription
idstringUnique identifier for the column.
labelReact.ReactNodeHeader label. If omitted, children render instead.
allowsSortingbooleanfalseEnables sort toggling on click.
align"left" | "center" | "right""left"Text alignment for the entire column.
tooltipstringOptional title attribute for the header.
isRowHeaderbooleanfalseMarks the column as a row header, rendering body cells as <th scope="row">.

Table.Body

PropTypeDefaultDescription
itemsT[]Data items to render.
childrenReact.ReactNode | (item: T, index: number) => React.ReactElement<Table.Row>Either a render function or static rows.

Table.Row

PropTypeDefaultDescription
idReact.KeyRow identifier used for selection.
isDisabledbooleanfalseDisables selection and hover styles for the row.

Table.Cell

PropTypeDefaultDescription
align"left" | "center" | "right""left"Overrides alignment for the cell.
isRowHeaderbooleanfalseForces the cell to render as <th scope="row">.

Types

type TableSelectionMode = "none" | "single" | "multiple";

type TableSortDirection = "ascending" | "descending";

type TableSortDescriptor = {
  column: string;
  direction: TableSortDirection;
};