Skip to content

Table

Shows data in rows and columns for comparing records. For a single record’s details, use Key value.

NameStatusOpen tasks
Website redesignActive8
Design systemActive14
Release notesDraft2
Code
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

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.

Two-line cells

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

EnvironmentStatus
Productioneu-central-1Healthy
Stagingus-east-1Deploying
Code
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.

ServiceRegionStatusAction
APIeu-central-1HealthyManage
us-east-1Deploying
Workereu-central-1HealthyManage
Code
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

PropTypeDefaultDescription
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

PropTypeDefaultDescription
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

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