Clidey UXDocs

Table

The table.tsx module provides a composable table. TableBody renders its rows normally by default; give the enclosing Table a maxHeight and TableBody automatically windows its rows so thousands of rows can be rendered without DOM overflow — no separate virtualized component required, and no risk of the header and body columns drifting out of alignment, since rows always stay in native table layout.

Interactive example#

Table · Live example

Project
Status
Progress
Action
Developer portal 02
In progress
26%
Developer portal 05
In progress
32%
Developer portal 08
In progress
38%
Developer portal 11
In progress
44%
Developer portal 14
In progress
50%
Developer portal 17
In progress
56%
Developer portal 20
In progress
62%
Developer portal 23
In progress
68%
Developer portal 26
In progress
74%
Developer portal 29
In progress
80%
Developer portal 32
In progress
86%
36 projects

Filter, sort, select a row, and scroll the automatically windowed table.

The code uses shared demo presentation helpers. Their complete source and styling are included in the LLM build context.

Import#

TSX

import {
  Table,
  TableHeader,
  TableBody,
  TableFooter,
  TableHead,
  TableRow,
  TableCell,
  TableCaption,
  TableHeadRow,
} from '@clidey/ux';

Basic table#

TSX

<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Email</TableHead>
      <TableHead>Role</TableHead>
      <TableHead className="text-right">Joined</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell className="font-medium">Jane Doe</TableCell>
      <TableCell>jane@example.com</TableCell>
      <TableCell><Badge>Admin</Badge></TableCell>
      <TableCell className="text-right">Jan 15, 2024</TableCell>
    </TableRow>
    <TableRow>
      <TableCell className="font-medium">John Smith</TableCell>
      <TableCell>john@example.com</TableCell>
      <TableCell><Badge variant="secondary">Member</Badge></TableCell>
      <TableCell className="text-right">Mar 2, 2024</TableCell>
    </TableRow>
  </TableBody>
</Table>

With caption#

TSX

<Table>
  <TableCaption>A list of recent invoices.</TableCaption>
  <TableHeader>
    {/* ... */}
  </TableHeader>
  <TableBody>
    {/* ... */}
  </TableBody>
</Table>

TSX

<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Item</TableHead>
      <TableHead className="text-right">Amount</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>Subscription</TableCell>
      <TableCell className="text-right">$49.00</TableCell>
    </TableRow>
    <TableRow>
      <TableCell>Add-on: Extra storage</TableCell>
      <TableCell className="text-right">$5.00</TableCell>
    </TableRow>
  </TableBody>
  <TableFooter>
    <TableRow>
      <TableCell>Total</TableCell>
      <TableCell className="text-right font-bold">$54.00</TableCell>
    </TableRow>
  </TableFooter>
</Table>

Virtualized table (large datasets)#

Pass maxHeight to Table to make it scrollable. TableBody then automatically renders only the rows near the visible area, plus lightweight spacer rows for the rest:

TSX

const COLUMNS = ['ID', 'Name', 'Email', 'Status'];
const rows = generateLargeDataset(10000); // e.g. 10,000 rows

<Table maxHeight={400}>
  <TableHeader>
    <TableHeadRow>
      {COLUMNS.map((col) => (
        <TableHead key={col}>{col}</TableHead>
      ))}
    </TableHeadRow>
  </TableHeader>
  <TableBody rowHeight={48} overscan={5}>
    {rows.map((row) => (
      <TableRow key={row.id}>
        <TableCell>{row.id}</TableCell>
        <TableCell>{row.name}</TableCell>
        <TableCell>{row.email}</TableCell>
        <TableCell><Badge>{row.status}</Badge></TableCell>
      </TableRow>
    ))}
  </TableBody>
</Table>

rowHeight is only an estimate used to compute which rows are near the viewport before their real height is measured — it accepts a number or a (index: number) => number function for variable-height rows. Actual rendered row heights are measured automatically and used once available, so rowHeight doesn't need to be exact.

Cell with JSON preview#

When a cell contains a large JSON object, TableCell renders an eye icon that opens a formatted JSON preview in a Drawer:

TSX

<TableCell>
  {JSON.stringify(largeObject)}
</TableCell>

Overflow detection is automatic — if the content exceeds the cell width, a preview icon appears.

Sub-components#

ComponentDescription
TableRoot <table> element. Accepts maxHeight to make the table scrollable and enable virtualization in TableBody.
TableHeader<thead> section.
TableBody<tbody> section. Renders all children normally, or virtualizes them when the enclosing Table has maxHeight set.
TableFooter<tfoot> section.
TableRow<tr> row element.
TableHead<th> header cell.
TableCell<td> data cell. Has overflow detection.
TableCaption<caption> for the table.
TableProviderContext provider used internally by Table.
TableHeadRowHeader row styled to match TableRow.

Table Props

PropTypeDefaultDescription
maxHeightnumber—Caps the table's height and makes it scrollable. Required for TableBody to virtualize its rows.

TableBody Props

PropTypeDefaultDescription
rowHeightnumber | ((index: number) => number)40Estimated height of each row in pixels, used before the real height is measured. Only relevant when the enclosing Table has maxHeight set.
overscannumber3Number of rows to render above and below the visible area. Only relevant when virtualized.
childrenReactNode—TableRow elements, same as a plain table body.

Was this page helpful?