Table

Import
import { Table } from "reshaped";
import type { TableProps } from "reshaped";
Storybook
Gives you full control over its composition

Can be integrated with 3rd party libraries for the table state management



Table is implemented as a collection of compound components that you can combine together 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 tbody tag. In case you want to separate table head and body, you can use Table.Head and Table.Body manually.

To turn the top row of the table into headings, you can use 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 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>

If you want to make your table cells compact or remove the padding completely, you can use 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, browser sets the columns width automatically based on the content you put inside and is not completely predictable. To have more granular control over the columns, you can use width and minWidth properties, which work with both number unit multipliers and literal values, like 50% or 200px. Use the auto width value if cell width should grow based on its content.

Note that you don't need to assign it to every cell. You can apply it just to your first table row and other columns will 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>

You can use the align property to align content horizontal inside each cell to start, center or end. Same way you can use the verticalAlign property to align the conrent vertically.

<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>

Default Table layout is very simple but it lets you combine with other components to achieve 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 data sets 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 3rd party libraries providing headless table state management, like TanStack Table. All you need to do is replace native table elements in their examples with Table compound components.