Table

Import
import { Table } from "reshaped";
import type { TableProps } from "reshaped";
Storybook

Table is implemented as a collection of compound components that you can combine and control based on your product logic. The simplest way to start using the table is to use its Table.Row and Table.Cell components.

<Table>
  <Table.Row>
    <Table.Cell>Cell 1</Table.Cell>
    <Table.Cell>Cell 2</Table.Cell>
  </Table.Row>
  <Table.Row>
    <Table.Cell>Cell 3</Table.Cell>
    <Table.Cell>Cell 4</Table.Cell>
  </Table.Row>
</Table>

When using Table.Row directly inside the Table, it will wrap the content with a tbody tag. If you want to separate the table head and body, you can manually use Table.Head and Table.Body.

To turn the top row of the table into headings, use the Table.Heading component instead of Table.Cell.

<Table>
  <Table.Row>
    <Table.Heading>Heading 1</Table.Heading>
    <Table.Heading>Heading 2</Table.Heading>
  </Table.Row>
  <Table.Row>
    <Table.Cell>Cell 1</Table.Cell>
    <Table.Cell>Cell 2</Table.Cell>
  </Table.Row>
</Table>

You can enable the outer border of the table and column borders with border and columnBorder flags.

<Table border columnBorder>
  <Table.Row>
    <Table.Heading>Heading 1</Table.Heading>
    <Table.Heading>Heading 2</Table.Heading>
  </Table.Row>
  <Table.Row>
    <Table.Cell>Cell 1</Table.Cell>
    <Table.Cell>Cell 2</Table.Cell>
  </Table.Row>
</Table>

You can highlight individual rows with a highlighted flag. For example, you can use it to highlight every other row for better content readability.

<Table border columnBorder>
  {[1, 2, 3, 4].map((i) => (
    <Table.Row key={i} highlighted={i % 2}>
      <Table.Cell>Cell {i} - 1</Table.Cell>
      <Table.Cell>Cell {i} - 2</Table.Cell>
    </Table.Row>
  ))}
</Table>

Like in regular HTML table elements, you can control column and row span with colSpan and rowSpan properties.

<Table border columnBorder>
  <Table.Row>
    <Table.Heading>Column 1</Table.Heading>
    <Table.Heading colSpan={2}>Column 2</Table.Heading>
  </Table.Row>
  <Table.Row>
    <Table.Cell>Cell 1</Table.Cell>
    <Table.Cell>Cell 2</Table.Cell>
    <Table.Cell rowSpan={2}>Cell 3</Table.Cell>
  </Table.Row>
  <Table.Row>
    <Table.Cell>Cell 1</Table.Cell>
    <Table.Cell>Cell 2</Table.Cell>
  </Table.Row>
</Table>

To make your table cells compact or remove the padding completely, use the padding, paddingInline, and paddingBlock properties.

<Table border columnBorder>
  <Table.Row>
    <Table.Cell padding={2}>Cell 1</Table.Cell>
    <Table.Cell padding={2}>Cell 2</Table.Cell>
  </Table.Row>
  <Table.Row>
    <Table.Cell padding={2}>Cell 3</Table.Cell>
    <Table.Cell padding={2}>Cell 4</Table.Cell>
  </Table.Row>
</Table>

By default, the browser sets the column width automatically based on the content, which can be unpredictable. For more precise control over the columns, use the width and minWidth properties, which accept both number unit multipliers and literal values like 50% or 200px. Use the auto width value if the cell width should grow based on its content.

You don't need to assign it to every cell. Applying it to just the first table row will make other columns respect that width value.

<Table border columnBorder>
  <Table.Row>
    <Table.Cell width="40%" minWidth="200px">
      Cell 1
    </Table.Cell>
    <Table.Cell>Cell 2</Table.Cell>
  </Table.Row>
  <Table.Row>
    <Table.Cell>Cell 3</Table.Cell>
    <Table.Cell>Cell 4</Table.Cell>
  </Table.Row>
</Table>

Use the align property to horizontally align content inside each cell to start, center, or end. Similarly, use the verticalAlign property to vertically align the content.

<Table border columnBorder>
  <Table.Row>
    <Table.Cell>Cell 1</Table.Cell>
    <Table.Cell align="end">Cell 2</Table.Cell>
  </Table.Row>
  <Table.Row>
    <Table.Cell>Cell 3</Table.Cell>
    <Table.Cell align="end">Cell 4</Table.Cell>
  </Table.Row>
</Table>

The default Table layout is very simple, but it allows you to combine it with other components to achieve the exact layouts you're building for your product. For example, you can combine it with the Card component to align it with other card sections on the page:

<Card elevated padding={0}>
  <Table>
    <Table.Row highlighted>
      <Table.Heading>Column 1</Table.Heading>
      <Table.Heading colSpan={2}>Column 2</Table.Heading>
    </Table.Row>
    <Table.Row>
      <Table.Cell>Cell 1</Table.Cell>
      <Table.Cell>Cell 2</Table.Cell>
      <Table.Cell align="end">Cell 3</Table.Cell>
    </Table.Row>
    <Table.Row>
      <Table.Cell>Cell 1</Table.Cell>
      <Table.Cell>Cell 2</Table.Cell>
      <Table.Cell align="end">Cell 3</Table.Cell>
    </Table.Row>
  </Table>
</Card>

When building products, Table can be used not only for static content but also for interactive datasets that support row selection, filtering, sorting, etc. Since Table is built with composition in mind, you can easily add your custom business logic and render it based on your state. For example, you can use it with Checkbox to make rows selectable:

function SelectionDemo() {
  const [value, setValue] = React.useState([]);
  const rows = [{ value: "1" }, { value: "2" }];
  const allSelected = value.length === rows.length;

  return (
    <Table>
      <Table.Row>
        <Table.Heading width="auto">
          <Checkbox
            inputAttributes={{ "aria-label": "Select all" }}
            name="all"
            checked={allSelected}
            indeterminate={!!value.length && value.length < rows.length}
            onChange={() => {
              setValue(allSelected ? [] : rows.map((row) => row.value));
            }}
          />
        </Table.Heading>
        <Table.Heading>Column 1</Table.Heading>
        <Table.Heading>Column 2</Table.Heading>
      </Table.Row>
      {rows.map((row) => (
        <Table.Row key={row.value} highlighted={value.includes(row.value)}>
          <Table.Cell>
            <Checkbox
              name="row"
              value={row.value}
              inputAttributes={{ "aria-label": "Select row" }}
              checked={value.includes(row.value)}
              onChange={(args) => {
                setValue((prev) => {
                  if (args.checked) return [...prev, args.value];
                  return prev.filter((item) => item !== args.value);
                });
              }}
            />
          </Table.Cell>
          <Table.Cell>Cell 1</Table.Cell>
          <Table.Cell>Cell 2</Table.Cell>
        </Table.Row>
      ))}
    </Table>
  );
}

Following the same approach, you can integrate Table with third-party libraries providing headless table state management, like TanStack Table. Replace native table elements in their examples with Table compound components.