Table

A real table, styled and made to scroll on narrow screens. Compound down to the cell, so the consumer decides what goes in each one.

API

Props

The container: border, radius, horizontal scroll, and the table element itself. Every part below takes the same three props, each defaulting to the element it stands for.

proptypedefault
as?ElementType"table"the element or component to render
className?stringmerged over the defaults, so it wins
…restprops of astyped against the chosen element

Table.Head

The header band, on its own background and separated by a rule.

proptypedefault
as?ElementType"thead"the element or component to render
className?stringmerged over the defaults, so it wins
…restprops of astyped against the chosen element

Table.Body

The rows, aligned to the top of their cells.

proptypedefault
as?ElementType"tbody"the element or component to render
className?stringmerged over the defaults, so it wins
…restprops of astyped against the chosen element

Table.Row

One row, carrying the separator below it — dropped on the last one.

proptypedefault
as?ElementType"tr"the element or component to render
className?stringmerged over the defaults, so it wins
…restprops of astyped against the chosen element

Table.HeadCell

A header cell.

proptypedefault
as?ElementType"th"the element or component to render
className?stringmerged over the defaults, so it wins
…restprops of astyped against the chosen element

Table.Cell

A body cell.

proptypedefault
as?ElementType"td"the element or component to render
className?stringmerged over the defaults, so it wins
…restprops of astyped against the chosen element

Examples

Usage

tsx
<Table>
  <Table.Head>
    <Table.Row>
      <Table.HeadCell>prop</Table.HeadCell>
      <Table.HeadCell>type</Table.HeadCell>
    </Table.Row>
  </Table.Head>
  <Table.Body>
    <Table.Row>
      <Table.Cell>guardIf</Table.Cell>
      <Table.Cell>boolean</Table.Cell>
    </Table.Row>
  </Table.Body>
</Table>
preview
proptypedefault
activebooleanfalse
asElementType"div"

Details

Compound down to the cell

The kit could have taken columns and rows as data and rendered the markup for you. It does not, because the moment one cell needs a chip, a link or a second line, a data-driven table either grows a render prop per column or stops being usable. Here the cells are yours from the start.

Mapping is therefore explicit, and pairs naturally with List — an empty array renders an empty body rather than a broken table.

tsx
<Table.Body>
  <List
    array={rows}
    itemExtractor={({ row }) => (
      <Table.Row key={row.id}>
        <Table.Cell>{row.name}</Table.Cell>
      </Table.Row>
    )}
  />
</Table.Body>

Cells do not wrap

Cells are whitespace-nowrap by default. A table that wraps its cells turns into a block of ragged text at the first narrow column, and the row loses the alignment that made it a table. When a column is genuinely prose — a description, a message — opt that one back into wrapping.

tsx
<Table.Cell className="whitespace-normal">{row.description}</Table.Cell>

Narrow screens

The container scrolls horizontally on its own, so a wide table never pushes the page sideways: the overflow belongs to the table and stops there. Beyond that, dropping a column is a decision only the consumer can make, since only the consumer knows which one is expendable.

tsx
<Table.HeadCell className="max-640:hidden" />
<Table.Cell className="max-640:hidden">{row.description}</Table.Cell>

That is what the props tables on this site do below 640: the description leaves and the three technical columns stay.

It stays a table

Each part renders its real element — table, thead, tbody, tr, th, td — rather than divs with a grid. Screen readers announce row and column positions, the browser handles headers, and none of that has to be reimplemented with aria attributes.