# Table (/docs/table)





`Table` is a thin, styled wrapper over a semantic `<table>`, sitting inside a bordered, rounded surface. It's a pure Server Component — no state, no `"use client"` — so it drops straight into an RSC tree or an MDX page.

You compose it from namespaced parts: an optional non-table header (`Table.Toolbar` + `Table.Title` for a title and actions), a `Table.Content` scroll region that holds the `<table>`, and the semantic parts `Table.Head` / `Table.Body` / `Table.Foot` / `Table.Row` / `Table.HeaderCell` / `Table.Cell`. The rounded border clips its content, so the corners stay intact even when the scroll region shows a scrollbar.

It carries no `variant`×`color` axis — a table is structural. The only knobs are `size` (density), `striped`, and `stickyHeader` / `stickyFooter`. For a data-driven, sortable/filterable table, reach for `DataTable`, which renders through this primitive.

```tsx
import { Table } from '@42-central/ui-react/table';

<Table>
  <Table.Toolbar>
    <Table.Title>Members</Table.Title>
    <Button size="sm">Add member</Button>
  </Table.Toolbar>
  <Table.Content>
    <Table.Head>
      <Table.Row>
        <Table.HeaderCell>Name</Table.HeaderCell>
        <Table.HeaderCell>Role</Table.HeaderCell>
        <Table.HeaderCell className="text-right">Commits</Table.HeaderCell>
      </Table.Row>
    </Table.Head>
    <Table.Body>
      <Table.Row>
        <Table.Cell>Ada Lovelace</Table.Cell>
        <Table.Cell>Staff</Table.Cell>
        <Table.Cell className="text-right">128</Table.Cell>
      </Table.Row>
    </Table.Body>
  </Table.Content>
</Table>
```

## Basic [#basic]

The table parts go inside `Table.Content`. Numeric columns read best right-aligned — pass `className="text-right"` to the header and cells (there's no `align` prop on the primitive; `DataTable` adds per-column alignment).

<Preview align="block" code="&#x22;<Table>\n  <Table.Content>\n    <Table.Head>\n      <Table.Row>\n        <Table.HeaderCell>Name</Table.HeaderCell>\n        <Table.HeaderCell>Role</Table.HeaderCell>\n        <Table.HeaderCell className=\&#x22;text-right\&#x22;>Commits</Table.HeaderCell>\n      </Table.Row>\n    </Table.Head>\n    <Table.Body>\n      <Table.Row>\n        <Table.Cell>Ada Lovelace</Table.Cell>\n        <Table.Cell>Staff</Table.Cell>\n        <Table.Cell className=\&#x22;text-right\&#x22;>128</Table.Cell>\n      </Table.Row>\n    </Table.Body>\n  </Table.Content>\n</Table>&#x22;">
  <Table>
    <Table.Content>
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell>
            Name
          </Table.HeaderCell>

          <Table.HeaderCell>
            Role
          </Table.HeaderCell>

          <Table.HeaderCell className="text-right">
            Commits
          </Table.HeaderCell>
        </Table.Row>
      </Table.Head>

      <Table.Body>
        <Table.Row>
          <Table.Cell>
            Ada Lovelace
          </Table.Cell>

          <Table.Cell>
            Staff
          </Table.Cell>

          <Table.Cell className="text-right">
            128
          </Table.Cell>
        </Table.Row>

        <Table.Row>
          <Table.Cell>
            Alan Turing
          </Table.Cell>

          <Table.Cell>
            Student
          </Table.Cell>

          <Table.Cell className="text-right">
            57
          </Table.Cell>
        </Table.Row>

        <Table.Row>
          <Table.Cell>
            Grace Hopper
          </Table.Cell>

          <Table.Cell>
            Staff
          </Table.Cell>

          <Table.Cell className="text-right">
            203
          </Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table.Content>
  </Table>
</Preview>

## Header with title & actions [#header-with-title--actions]

`Table.Toolbar` is a non-`<table>` header bar above the scroll region — a title on the left (`Table.Title`) and any actions on the right. It's separated from the table by a border and stays put when the body scrolls.

<Preview align="block" code="&#x22;<Table>\n  <Table.Toolbar>\n    <Table.Title>Members</Table.Title>\n    <Button size=\&#x22;sm\&#x22;>Add member</Button>\n  </Table.Toolbar>\n  <Table.Content>{/* … */}</Table.Content>\n</Table>&#x22;">
  <Table>
    <Table.Toolbar>
      <Table.Title>
        Members
      </Table.Title>

      <Button size="sm">
        Add member
      </Button>
    </Table.Toolbar>

    <Table.Content>
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell>
            Name
          </Table.HeaderCell>

          <Table.HeaderCell>
            Role
          </Table.HeaderCell>
        </Table.Row>
      </Table.Head>

      <Table.Body>
        <Table.Row>
          <Table.Cell>Ada Lovelace</Table.Cell>

          <Table.Cell>Staff</Table.Cell>
        </Table.Row>

        <Table.Row>
          <Table.Cell>Alan Turing</Table.Cell>

          <Table.Cell>Student</Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table.Content>
  </Table>
</Preview>

## Striped [#striped]

`striped` tints even body rows — easier to scan across wide rows. It targets `tbody` rows only, so the header is untouched.

<Preview align="block" code="&#x22;<Table striped>\n  <Table.Content>{/* … */}</Table.Content>\n</Table>&#x22;">
  <Table>
    <Table.Content>
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell>
            Name
          </Table.HeaderCell>

          <Table.HeaderCell>
            Role
          </Table.HeaderCell>
        </Table.Row>
      </Table.Head>

      <Table.Body>
        <Table.Row>
          <Table.Cell>Ada Lovelace</Table.Cell>

          <Table.Cell>Staff</Table.Cell>
        </Table.Row>

        <Table.Row>
          <Table.Cell>Alan Turing</Table.Cell>

          <Table.Cell>Student</Table.Cell>
        </Table.Row>

        <Table.Row>
          <Table.Cell>Grace Hopper</Table.Cell>

          <Table.Cell>Staff</Table.Cell>
        </Table.Row>

        <Table.Row>
          <Table.Cell>Katherine Johnson</Table.Cell>

          <Table.Cell>Staff</Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table.Content>
  </Table>
</Preview>

## Density [#density]

`size` scales cell padding and text from `xs` to `xl` (default `md`), reusing the kit's canonical `Size` scale.

<Preview align="block" code="&#x22;<Table size=\&#x22;sm\&#x22;>{/* … */}</Table>\n<Table size=\&#x22;lg\&#x22;>{/* … */}</Table>&#x22;">
  <div className="flex flex-col gap-6">
    <Table size="sm">
      <Table.Content>
        <Table.Head>
          <Table.Row>
            <Table.HeaderCell>Compact (sm)</Table.HeaderCell>

            <Table.HeaderCell>Role</Table.HeaderCell>
          </Table.Row>
        </Table.Head>

        <Table.Body>
          <Table.Row>
            <Table.Cell>Ada Lovelace</Table.Cell>

            <Table.Cell>Staff</Table.Cell>
          </Table.Row>

          <Table.Row>
            <Table.Cell>Alan Turing</Table.Cell>

            <Table.Cell>Student</Table.Cell>
          </Table.Row>
        </Table.Body>
      </Table.Content>
    </Table>

    <Table size="lg">
      <Table.Content>
        <Table.Head>
          <Table.Row>
            <Table.HeaderCell>Roomy (lg)</Table.HeaderCell>

            <Table.HeaderCell>Role</Table.HeaderCell>
          </Table.Row>
        </Table.Head>

        <Table.Body>
          <Table.Row>
            <Table.Cell>Ada Lovelace</Table.Cell>

            <Table.Cell>Staff</Table.Cell>
          </Table.Row>

          <Table.Row>
            <Table.Cell>Alan Turing</Table.Cell>

            <Table.Cell>Student</Table.Cell>
          </Table.Row>
        </Table.Body>
      </Table.Content>
    </Table>
  </div>
</Preview>

## Sticky header & footer [#sticky-header--footer]

`stickyHeader` pins the header to the top while the body scrolls; `stickyFooter` pins a `Table.Foot` row (a totals/summary row) to the bottom. Both need a bounded height on the scroll region — set `max-h-*` on `Table` (bounds the whole surface, toolbar included) or on `Table.Content` (bounds just the scroll region). The separators are cell borders (`border-collapse: separate`), so they stay visible while the header/footer are pinned, and the rounded border keeps its corners around the scrollbar.

<Preview align="block" code="&#x22;<Table stickyHeader stickyFooter striped className=\&#x22;max-h-64\&#x22;>\n  <Table.Content>\n    <Table.Head>{/* … */}</Table.Head>\n    <Table.Body>{/* many rows */}</Table.Body>\n    <Table.Foot>{/* pinned totals row */}</Table.Foot>\n  </Table.Content>\n</Table>&#x22;">
  <Table className="max-h-64">
    <Table.Content>
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell>
            \#
          </Table.HeaderCell>

          <Table.HeaderCell>
            Name
          </Table.HeaderCell>

          <Table.HeaderCell className="text-right">
            Commits
          </Table.HeaderCell>
        </Table.Row>
      </Table.Head>

      <Table.Body>
        {Array.from({ length: 20 }, (_, i) => (
                  <Table.Row key={i}>
                    <Table.Cell>{i + 1}</Table.Cell>
                    <Table.Cell>Contributor {i + 1}</Table.Cell>
                    <Table.Cell className="text-right">{(i + 1) * 7}</Table.Cell>
                  </Table.Row>
                ))}
      </Table.Body>

      <Table.Foot>
        <Table.Row>
          <Table.Cell>
            Total
          </Table.Cell>

          <Table.Cell />

          <Table.Cell className="text-right">
            1470
          </Table.Cell>
        </Table.Row>
      </Table.Foot>
    </Table.Content>
  </Table>
</Preview>

## Horizontal + vertical scroll [#horizontal--vertical-scroll]

`Table.Content` scrolls on both axes at once. A wide table (more columns than fit) scrolls
horizontally, while a bounded height (`max-h-*`) plus `stickyHeader` scrolls it vertically with
the header pinned — the header scrolls sideways in lockstep with the body so columns stay
aligned. Both scrollbars are clipped by the rounded container, so its corners stay clean.
Keep cell content on one line with `whitespace-nowrap` so columns don't wrap instead of
overflowing.

A soft shadow appears on each horizontal side that still has content to scroll to, and fades
out as you reach that edge — a scroll affordance driven purely by scroll position in CSS (no
JS, so `Table` stays a Server Component). Browsers without scroll-driven animations simply
show no fade.

<Preview align="block" code="&#x22;<Table stickyHeader className=\&#x22;max-h-72\&#x22;>\n  <Table.Content classNames={{ table: 'min-w-[48rem]' }}>\n    <Table.Head>\n      <Table.Row>\n        <Table.HeaderCell>#</Table.HeaderCell>\n        <Table.HeaderCell>Name</Table.HeaderCell>\n        <Table.HeaderCell>Email</Table.HeaderCell>\n        {/* …more columns… */}\n      </Table.Row>\n    </Table.Head>\n    <Table.Body>{/* many wide rows */}</Table.Body>\n  </Table.Content>\n</Table>&#x22;">
  <Table className="max-h-72">
    <Table.Content classNames="{ table: 'min-w-[48rem]' }">
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell>
            \#
          </Table.HeaderCell>

          <Table.HeaderCell className="whitespace-nowrap">
            Name
          </Table.HeaderCell>

          <Table.HeaderCell className="whitespace-nowrap">
            Email
          </Table.HeaderCell>

          <Table.HeaderCell className="whitespace-nowrap">
            Role
          </Table.HeaderCell>

          <Table.HeaderCell className="whitespace-nowrap">
            Team
          </Table.HeaderCell>

          <Table.HeaderCell className="whitespace-nowrap">
            Location
          </Table.HeaderCell>

          <Table.HeaderCell className="whitespace-nowrap text-right">
            Commits
          </Table.HeaderCell>

          <Table.HeaderCell className="whitespace-nowrap text-right">
            PRs
          </Table.HeaderCell>

          <Table.HeaderCell className="whitespace-nowrap text-right">
            Reviews
          </Table.HeaderCell>

          <Table.HeaderCell className="whitespace-nowrap">
            Last active
          </Table.HeaderCell>
        </Table.Row>
      </Table.Head>

      <Table.Body>
        {Array.from({ length: 18 }, (_, i) => (
                  <Table.Row key={i}>
                    <Table.Cell>{i + 1}</Table.Cell>
                    <Table.Cell className="whitespace-nowrap">Contributor {i + 1}</Table.Cell>
                    <Table.Cell className="whitespace-nowrap">user{i + 1}@42.tech</Table.Cell>
                    <Table.Cell>{i % 3 === 0 ? 'Staff' : 'Student'}</Table.Cell>
                    <Table.Cell className="whitespace-nowrap">{['Order', 'Aliance', 'Federation', 'Assembly'].at(i % 4)}</Table.Cell>
                    <Table.Cell className="whitespace-nowrap">Paris, FR</Table.Cell>
                    <Table.Cell className="text-right">{(i + 1) * 7}</Table.Cell>
                    <Table.Cell className="text-right">{(i + 1) * 2}</Table.Cell>
                    <Table.Cell className="text-right">{(i + 1) * 3}</Table.Cell>
                    <Table.Cell className="whitespace-nowrap">2024-{String((i % 12) + 1).padStart(2, '0')}-15</Table.Cell>
                  </Table.Row>
                ))}
      </Table.Body>
    </Table.Content>
  </Table>
</Preview>

## Summary footer [#summary-footer]

`Table.Foot` renders a `<tfoot>` with a top separator — a natural home for totals.

<Preview align="block" code="&#x22;<Table>\n  <Table.Content>\n    <Table.Head>{/* … */}</Table.Head>\n    <Table.Body>{/* … */}</Table.Body>\n    <Table.Foot>\n      <Table.Row>\n        <Table.Cell>Total</Table.Cell>\n        <Table.Cell className=\&#x22;text-right\&#x22;>388</Table.Cell>\n      </Table.Row>\n    </Table.Foot>\n  </Table.Content>\n</Table>&#x22;">
  <Table>
    <Table.Content>
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell>
            Name
          </Table.HeaderCell>

          <Table.HeaderCell className="text-right">
            Commits
          </Table.HeaderCell>
        </Table.Row>
      </Table.Head>

      <Table.Body>
        <Table.Row>
          <Table.Cell>Ada Lovelace</Table.Cell>

          <Table.Cell className="text-right">128</Table.Cell>
        </Table.Row>

        <Table.Row>
          <Table.Cell>Alan Turing</Table.Cell>

          <Table.Cell className="text-right">57</Table.Cell>
        </Table.Row>

        <Table.Row>
          <Table.Cell>Grace Hopper</Table.Cell>

          <Table.Cell className="text-right">203</Table.Cell>
        </Table.Row>
      </Table.Body>

      <Table.Foot>
        <Table.Row>
          <Table.Cell>
            Total
          </Table.Cell>

          <Table.Cell className="text-right">
            388
          </Table.Cell>
        </Table.Row>
      </Table.Foot>
    </Table.Content>
  </Table>
</Preview>

## Subcomponents [#subcomponents]

<TypeTable
  type="{
  Table: {
    type: 'component',
    description: 'Root — a bordered, rounded, clipped surface. Takes `size`, `striped`, `stickyHeader`, `stickyFooter`; `className` targets this container.',
  },
  'Table.Toolbar': {
    type: 'component',
    description: 'Optional non-table header bar (title + actions) above the scroll region.',
  },
  'Table.Title': {
    type: 'component',
    description: 'Heading rendered as an `h3`, for use inside Table.Toolbar.',
  },
  'Table.Content': {
    type: 'component',
    description: 'The scroll region that holds the `<table>`. `className` sizes it (e.g. `max-h-*`); `classNames.table` styles the `<table>`.',
  },
  'Table.Head': {
    type: 'component',
    description: 'Renders a `<thead>` (subtle fill + bottom separator).',
  },
  'Table.Body': {
    type: 'component',
    description: 'Renders a `<tbody>`.',
  },
  'Table.Foot': {
    type: 'component',
    description: 'Renders a `<tfoot>` (subtle fill + top separator); good for summary rows.',
  },
  'Table.Row': {
    type: 'component',
    description: 'Renders a `<tr>`.',
  },
  'Table.HeaderCell': {
    type: 'component',
    description: 'Renders a `<th>`; defaults `scope=&#x22;col&#x22;` (overridable to `&#x22;row&#x22;`).',
  },
  'Table.Cell': {
    type: 'component',
    description: 'Renders a `<td>`.',
  },
}"
/>

## API [#api]

<TypeTable
  type="{
  size: {
    type: &#x22;'xs' | 'sm' | 'md' | 'lg' | 'xl'&#x22;,
    default: &#x22;'md'&#x22;,
    description: 'Density — scales cell padding and text.',
  },
  striped: {
    type: 'boolean',
    default: 'false',
    description: 'Tints even body rows for scannability.',
  },
  stickyHeader: {
    type: 'boolean',
    default: 'false',
    description: 'Pins the header row during vertical scroll. Needs a bounded height (max-h-*) on Table or Table.Content.',
  },
  stickyFooter: {
    type: 'boolean',
    default: 'false',
    description: 'Pins a Table.Foot row (e.g. totals) to the bottom during vertical scroll. Same bounded-height requirement.',
  },
  className: {
    type: 'string',
    description: 'Styles the outer container (border, rounding, height). Use Table.Content for the scroll region and part components for the table internals.',
  },
  asChild: {
    type: 'boolean',
    default: 'false',
    description: 'Render children as the root container element (Ark UI polymorphism).',
  },
  testId: {
    type: 'string',
    description: 'data-testid on this container. Every part (Table.Row, Table.Cell, Table.HeaderCell, …) takes its own testId prop too — see the Testing guide.',
  },
}"
/>
