Table
Shows data in rows and columns for comparing records. For a single record’s details, use Key value.
| Name | Status | Open tasks |
|---|---|---|
| Website redesign | Active | 8 |
| Design system | Active | 14 |
| Release notes | Draft | 2 |
Code
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.
| Environment | Status |
|---|---|
| Productioneu-central-1 | Healthy |
| Stagingus-east-1 | Deploying |
Code
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.
Code
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.