# Table

Shows data in rows and columns for comparing records. For a single record's details, use [Key value](/components/key-value.md).

```tsx
// demo.tsx
import {Table} from '@earendil-works/elanor-react';

const projects = [
  {name: 'Website redesign', status: 'Active', tasks: 8},
  {name: 'Design system', status: 'Active', tasks: 14},
  {name: 'Release notes', status: 'Draft', tasks: 2},
];

export default function TableDemo() {
  return (
    <Table.Container>
      <Table.Root aria-label="Projects">
        <Table.Head>
          <Table.Row>
            <Table.HeaderCell scope="col">Name</Table.HeaderCell>
            <Table.HeaderCell scope="col">Status</Table.HeaderCell>
            <Table.HeaderCell scope="col" align="end">
              Open tasks
            </Table.HeaderCell>
          </Table.Row>
        </Table.Head>
        <Table.Body>
          {projects.map((project) => (
            <Table.Row key={project.name}>
              <Table.Cell>{project.name}</Table.Cell>
              <Table.Cell>{project.status}</Table.Cell>
              <Table.Cell align="end">{project.tasks}</Table.Cell>
            </Table.Row>
          ))}
        </Table.Body>
      </Table.Root>
    </Table.Container>
  );
}
```

## Usage

```tsx
import {Table} from '@earendil-works/elanor-react';

<Table.Container>
  <Table.Root aria-label="Projects">
    <Table.Head>
      <Table.Row>
        <Table.HeaderCell scope="col" />
      </Table.Row>
    </Table.Head>
    <Table.Body>
      <Table.Row>
        <Table.Cell />
      </Table.Row>
    </Table.Body>
  </Table.Root>
</Table.Container>;
```

The parts render native table elements. Container scrolls wide tables horizontally. Name the table with `aria-label` or a caption, and set `scope` on header cells. Align numbers with `align="end"` on both the header cell and the column's cells, so the header lines up with its values.

Cells align to the top, so short cells line up with the first line of taller ones. Table text uses `semantic.typography.primary`, the UI style by default and the prose style inside long-form content (`data-text-context="prose"`), like [Text](/components/text.md).

## Two-line cells

`CellPrimary` and `CellSecondary` stack a main value over supporting text in one cell.

```tsx
// two-line-cells.tsx
import {Table} from '@earendil-works/elanor-react';

export default function TableTwoLineCells() {
  return (
    <Table.Container>
      <Table.Root aria-label="Environments">
        <Table.Head>
          <Table.Row>
            <Table.HeaderCell scope="col">Environment</Table.HeaderCell>
            <Table.HeaderCell scope="col">Status</Table.HeaderCell>
          </Table.Row>
        </Table.Head>
        <Table.Body>
          <Table.Row>
            <Table.Cell>
              <Table.CellPrimary>Production</Table.CellPrimary>
              <Table.CellSecondary>eu-central-1</Table.CellSecondary>
            </Table.Cell>
            <Table.Cell>Healthy</Table.Cell>
          </Table.Row>
          <Table.Row>
            <Table.Cell>
              <Table.CellPrimary>Staging</Table.CellPrimary>
              <Table.CellSecondary>us-east-1</Table.CellSecondary>
            </Table.Cell>
            <Table.Cell>Deploying</Table.Cell>
          </Table.Row>
        </Table.Body>
      </Table.Root>
    </Table.Container>
  );
}
```

## Cells that span rows

Cells with `rowSpan` fill the same column in the rows below them. The first and last cell of a row drop their outer padding to line up with the table's edges, so tell later rows which edges spanning cells take with `spannedEdges`. Their first or last cell then keeps its padding and stays in line with its column.

```tsx
// spanning-rows.tsx
import {Link, Table} from '@earendil-works/elanor-react';

const services = [
  {
    name: 'API',
    deployments: [
      {region: 'eu-central-1', status: 'Healthy'},
      {region: 'us-east-1', status: 'Deploying'},
    ],
  },
  {name: 'Worker', deployments: [{region: 'eu-central-1', status: 'Healthy'}]},
];

export default function TableSpanningRows() {
  return (
    <Table.Container>
      <Table.Root aria-label="Deployments">
        <Table.Head>
          <Table.Row>
            <Table.HeaderCell scope="col">Service</Table.HeaderCell>
            <Table.HeaderCell scope="col">Region</Table.HeaderCell>
            <Table.HeaderCell scope="col">Status</Table.HeaderCell>
            <Table.HeaderCell scope="col" align="end">
              Action
            </Table.HeaderCell>
          </Table.Row>
        </Table.Head>
        <Table.Body>
          {services.flatMap(({name, deployments}) =>
            deployments.map(({region, status}, index) => (
              <Table.Row key={`${name}-${region}`} spannedEdges={index > 0 ? 'both' : undefined}>
                {index === 0 ? <Table.Cell rowSpan={deployments.length}>{name}</Table.Cell> : null}
                <Table.Cell>{region}</Table.Cell>
                <Table.Cell>{status}</Table.Cell>
                {index === 0 ? (
                  <Table.Cell rowSpan={deployments.length} align="end">
                    <Link href="#">Manage</Link>
                  </Table.Cell>
                ) : null}
              </Table.Row>
            )),
          )}
        </Table.Body>
      </Table.Root>
    </Table.Container>
  );
}
```

## API

### `Table.Container`

Renders `<div>`. Accepts its HTML attributes and `ref`.

### `Table.Root`

Renders `<table>`. Accepts its HTML attributes and `ref`.

### `Table.Head`

Renders `<thead>`. Accepts its HTML attributes and `ref`.

### `Table.Row`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `spannedEdges` | `'start' \| 'end' \| 'both'` | - | Table edges taken by cells that span down from an earlier row with `rowSpan`. The row's first or last cell on that edge then sits inside the table and keeps its inner padding. |

Renders `<tr>`. Also accepts its HTML attributes and `ref`.

### `Table.HeaderCell`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `'start' \| 'center' \| 'end'` | `'start'` | Inline alignment of the header text. Match the column's cells. |

Renders `<th>`. Also accepts its HTML attributes and `ref`.

### `Table.Body`

Renders `<tbody>`. Accepts its HTML attributes and `ref`.

### `Table.Cell`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `'start' \| 'center' \| 'end'` | `'start'` | Inline alignment of the cell content. Use `end` for numbers. |

Renders `<td>`. Also accepts its HTML attributes and `ref`.

### `Table.CellPrimary`

Renders `<span>`. Accepts its HTML attributes and `ref`.

### `Table.CellSecondary`

Renders `<span>`. Accepts its HTML attributes and `ref`.
