---
id: "packages/active-ui/table"
title: "Table"
description: "Rows and columns of records or of text, with a header, a footer, a caption, and a container that scrolls only when it must."
url: "/packages/active-ui/table"
locale: "en"
revision: "764cee434e05d1bb"
---

# Table

`Table` is a styled HTML table for data that has rows and columns: records with their figures, or
a reference of names and meanings. It renders real `<table>` semantics. For thousands of rows with
sorting, filtering and server paging, a data grid is the tool; for a list of label and value pairs,
a description list.

**Example: A data table and a text table**

Client: Runs entirely in your browser. Source: `examples/ui/table.tsx`.

```tsx
// apps/docs/examples/ui/table.tsx
'use client';

import {formatCurrency, formatNumber} from '@workspace/active-format';
import {Code} from '@workspace/active-ui/components/composed/code';
import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableFooter,
  TableHead,
  TableHeader,
  TableRow,
} from '@workspace/active-ui/components/primitives/table';

// Fixed figures, formatted by active-format in the language of the page.
const SCHEMES = [
  {name: 'Pension scheme 2026', participants: 214, contributions: 1284500.5},
  {name: 'Bridging scheme', participants: 38, contributions: 96300},
  {name: 'Early retirement', participants: 7, contributions: 12250.75},
];
const money = (amount: number) =>
  formatCurrency({amount, currency: 'EUR'}, 'en');

const SETTINGS = [
  ['dateFormat', 'How a date is written, such as DMY_NUMERIC.'],
  [
    'numberFormat',
    'The separators of a number, such as COMMA_DECIMAL_DOT_GROUP.',
  ],
];

/**
 * A data table: one row per record, numbers aligned to the right, a footer
 * with the totals and a caption. Below it a text table (`layout="text"`),
 * whose cells wrap and keep identifiers whole.
 */
export default function TableExample() {
  const total = SCHEMES.reduce((sum, s) => sum + s.contributions, 0);
  return (
    <div className="flex w-full max-w-xl flex-col gap-8">
      <Table>
        <TableCaption>Contributions collected in 2026</TableCaption>
        <TableHeader>
          <TableRow>
            <TableHead>Scheme</TableHead>
            <TableHead className="text-right">Participants</TableHead>
            <TableHead className="text-right">Contributions</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {SCHEMES.map((scheme) => (
            <TableRow key={scheme.name}>
              <TableCell className="font-medium">{scheme.name}</TableCell>
              <TableCell className="text-right tabular-nums">
                {formatNumber(scheme.participants, 'integer', 'en')}
              </TableCell>
              <TableCell className="text-right tabular-nums">
                {money(scheme.contributions)}
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
        <TableFooter>
          <TableRow>
            <TableCell colSpan={2}>Total</TableCell>
            <TableCell className="text-right tabular-nums">
              {money(total)}
            </TableCell>
          </TableRow>
        </TableFooter>
      </Table>

      <Table layout="text" scrollLabel="Settings, scrolls sideways">
        <TableHeader>
          <TableRow>
            <TableHead>Setting</TableHead>
            <TableHead>Meaning</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {SETTINGS.map(([name, meaning]) => (
            <TableRow key={name}>
              <TableCell>
                <Code>{name}</Code>
              </TableCell>
              <TableCell>{meaning}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}
```

## Usage

```tsx
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@workspace/active-ui/components/primitives/table';

<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Scheme</TableHead>
      <TableHead className="text-right">Participants</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>Pension scheme 2026</TableCell>
      <TableCell className="text-right tabular-nums">214</TableCell>
    </TableRow>
  </TableBody>
</Table>;
```

## Two layouts

- **`layout="data"`** (the default): every cell on one line, vertically centred, at the compact
  size. For records and figures.
- **`layout="text"`**: cells wrap and align to the top, at the base text size. For a reference or a
  catalogue whose cells hold sentences. Identifiers in [`Code`](/packages/active-ui/code) stay
  whole while the table fits; only when it does not are they split at their readable break
  points (`_`, `.`, camelCase), never inside a word. This site renders every Markdown table this
  way.

In both layouts a table that does not fit its column scrolls inside its own container, never the
page. While it scrolls, the container is a keyboard stop; with `scrollLabel` it is also a named
region.

## Anatomy

- `Table`: the `<table>` in its container. `layout` and `scrollLabel`.
- `TableHeader`, `TableBody`, `TableFooter`: `<thead>`, `<tbody>`, `<tfoot>`.
- `TableRow`, `TableHead` (`<th>`), `TableCell` (`<td>`), `TableCaption` (`<caption>`, below the
  table).

## What your application provides

- Formatted values: numbers, amounts and dates come formatted, for example with
  [active-format](/packages/active-format). Align figures to the right with `text-right` and
  `tabular-nums`.
- `scope="col"` on header cells of a complex table, and a caption or a heading that says what the
  table holds.
- `scrollLabel` for a table that may be wider than its column, in your reader's language.

## Accessibility and keyboard

- Real table semantics: screen readers announce rows, columns and headers.
- A container that scrolls takes focus, so the keyboard can scroll it with the arrow keys; a table
  that fits adds no tab stop.

## API

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `layout` | `"data" \| "text"` | no | `data`: every cell on one line, vertically centred, compact. `text`: cells wrap and align to the top at the base size, and identifiers in `Code` stay whole while the table fits. |
| `scrollLabel` | `string` | no | The name of the scroll container while the table is wider than it is, such as "Table, scrolls sideways"; it then becomes a named region. |

### Other parts

**TableHeader**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |

TableHeader has no props of its own.

**TableBody**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |

TableBody has no props of its own.

**TableFooter**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |

TableFooter has no props of its own.

**TableHead**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |

TableHead has no props of its own.

**TableRow**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |

TableRow has no props of its own.

**TableCell**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |

TableCell has no props of its own.

**TableCaption**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |

TableCaption has no props of its own.

> [!NOTE]
> **Based on shadcn/ui**
>
> This page adapts the Table documentation of shadcn/ui (MIT licence) to this
> package; see [Third-party notices](/third-party-notices).
