# Table

A semantic table in a scroll container, with sortable headers, loading, empty and error rows.

Source page: https://sproutboat.com/ikada/components/table

## Installation
```sh
npx shadcn@latest add https://sproutboat.com/ikada/r/table.json
```
Or from the package: `import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell, TableEmpty, TableError, TableLoading } from "@sproutboat/ikada";`
## Usage
```tsx
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell, TableEmpty, TableError, TableLoading } from "@/components/ikada/table";

<Table label="Sprouts">
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Region</TableHead>
      <TableHead>Status</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>robin</TableCell>
      <TableCell>eu-west</TableCell>
      <TableCell>
        <StatusDot tone="success" live label="Live" />
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>wren</TableCell>
      <TableCell>us-east</TableCell>
      <TableCell>
        <StatusDot tone="success" live label="Live" />
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>finch</TableCell>
      <TableCell>eu-west</TableCell>
      <TableCell>
        <StatusDot tone="info" pulse label="Building" />
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>heron</TableCell>
      <TableCell>us-east</TableCell>
      <TableCell>
        <StatusDot tone="neutral" label="Paused" />
      </TableCell>
    </TableRow>
  </TableBody>
</Table>
```
## Examples
### Basic
Wide tables scroll inside their container, never the page. 48px rows.
```tsx
<Table label="Sprouts">
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Region</TableHead>
      <TableHead>Status</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>robin</TableCell>
      <TableCell>eu-west</TableCell>
      <TableCell>
        <StatusDot tone="success" live label="Live" />
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>wren</TableCell>
      <TableCell>us-east</TableCell>
      <TableCell>
        <StatusDot tone="success" live label="Live" />
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>finch</TableCell>
      <TableCell>eu-west</TableCell>
      <TableCell>
        <StatusDot tone="info" pulse label="Building" />
      </TableCell>
    </TableRow>
    <TableRow>
      <TableCell>heron</TableCell>
      <TableCell>us-east</TableCell>
      <TableCell>
        <StatusDot tone="neutral" label="Paused" />
      </TableCell>
    </TableRow>
  </TableBody>
</Table>
```
### Numbers and long names
Number columns take `numeric`: right-aligned tabular figures, so digits line up. Long names scroll inside the table.
```tsx
<TableHead numeric>Requests</TableHead>
<TableCell numeric>1,284,031</TableCell>
```
### Sortable
A sortable header holds a button and sets aria-sort. nextSort cycles none, ascending, descending. Updated hides below the container's md width.
```tsx
const [sort, setSort] = useState<SortDirection | undefined>("asc");
// rows: your data, ordered by sort (descending reverses it)
const rows = [
  { name: "robin", region: "eu-west", updated: "3 min ago" },
  { name: "wren", region: "us-east", updated: "2 h ago" },
];

<Table label="Sprouts">
  <TableHeader>
    <TableRow>
      <TableHead sortable sort={sort} onSort={() => setSort(nextSort(sort))}>
        Name
      </TableHead>
      <TableHead>Region</TableHead>
      <TableHead hideBelow="md">Updated</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    {rows.map((s) => (
      <TableRow key={s.name}>
        <TableCell>{s.name}</TableCell>
        <TableCell>{s.region}</TableCell>
        <TableCell hideBelow="md">{s.updated}</TableCell>
      </TableRow>
    ))}
  </TableBody>
</Table>
```
### Selected row
Highlights the row and sets aria-selected. Pair it with a checkbox cell: that is the control.
```tsx
<Table label="Sprouts">
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Region</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow selected>
      <TableCell>robin</TableCell>
      <TableCell>eu-west</TableCell>
    </TableRow>
    <TableRow>
      <TableCell>wren</TableCell>
      <TableCell>us-east</TableCell>
    </TableRow>
  </TableBody>
</Table>
```
### Sticky header
The header stays put while the body scrolls. Pair stickyHeader with a maxHeight.
```tsx
const sprouts = [
  { name: "robin", region: "eu-west" },
  { name: "wren", region: "us-east" },
  // …
];

<Table label="Sprouts" stickyHeader maxHeight="9rem">
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Region</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    {sprouts.map((s) => (
      <TableRow key={s.name}>
        <TableCell>{s.name}</TableCell>
        <TableCell>{s.region}</TableCell>
      </TableRow>
    ))}
  </TableBody>
</Table>
```
### Loading
Skeleton rows keep the layout of the loaded table. Mark the table aria-busy.
```tsx
<Table label="Sprouts" aria-busy="true">
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Region</TableHead>
      <TableHead>Status</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableLoading columns={3} rows={3} />
  </TableBody>
</Table>
```
### Empty

```tsx
<Table label="Sprouts">
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Region</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableEmpty colSpan={2}>No sprouts yet. Plant your first one.</TableEmpty>
  </TableBody>
</Table>
```
### Error

```tsx
<Table label="Sprouts">
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Region</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableError colSpan={2} action={<Button size="sm">Retry</Button>}>
      The API did not answer in time.
    </TableError>
  </TableBody>
</Table>
```
## Props
### HideBelowProps

| Prop | Type | Description |
| --- | --- | --- |
| hideBelow? | `TableBreakpoint` | Drops this column (set it on its `TableHead` and every `TableCell`) when the table's container is narrower than the breakpoint. Use it for low-priority columns only; never put critical status (failure) in a column that can hide; the container still scrolls sideways as the last resort. |
### TableProps
Also takes every prop of `ComponentProps<"table">`.
| Prop | Type | Description |
| --- | --- | --- |
| label | `string` | Names the scrolling region for screen readers. Required: say what the table lists. |
| stickyHeader? | `boolean` | Header stays put while the body scrolls. Pair with `maxHeight`. Default `false`. |
| maxHeight? | `string` | CSS max-height of the scroll area, e.g. "24rem". |
| containerClassName? | `string` | Applied to the scroll container. |
### TableRowProps
Also takes every prop of `ComponentProps<"tr">`.
| Prop | Type | Description |
| --- | --- | --- |
| selected? | `boolean` | Highlights the row and sets aria-selected. Pair with a checkbox cell: that is the control. Default `false`. |
### NumericProps

| Prop | Type | Description |
| --- | --- | --- |
| numeric? | `boolean` | Right-aligns the column (start edge in RTL reads end) and sets tabular figures. Set it on the `TableHead` and every `TableCell` of a number column. |
### TableHeadProps
Also takes every prop of `Omit<ComponentProps<"th">, "onClick" | "aria-sort">, HideBelowProps, NumericProps`.
| Prop | Type | Description |
| --- | --- | --- |
| sortable? | `boolean` | Makes the header a button. `undefined` means sortable but not sorted. Default `false`. |
| sort? | `SortDirection` | Current sort of this column; shows the arrow and sets aria-sort. |
| onSort? | `(() => void)` | Makes the header a sort button; called when it is pressed. |
| hideBelow? | `TableBreakpoint` | Drops this column (set it on its `TableHead` and every `TableCell`) when the table's container is narrower than the breakpoint. Use it for low-priority columns only; never put critical status (failure) in a column that can hide; the container still scrolls sideways as the last resort. |
| numeric? | `boolean` | Right-aligns the column (start edge in RTL reads end) and sets tabular figures. Set it on the `TableHead` and every `TableCell` of a number column. |
### TableCellProps

| Prop | Type | Description |
| --- | --- | --- |
| hideBelow? | `TableBreakpoint` | Drops this column (set it on its `TableHead` and every `TableCell`) when the table's container is narrower than the breakpoint. Use it for low-priority columns only; never put critical status (failure) in a column that can hide; the container still scrolls sideways as the last resort. |
| numeric? | `boolean` | Right-aligns the column (start edge in RTL reads end) and sets tabular figures. Set it on the `TableHead` and every `TableCell` of a number column. |
### TableEmptyProps

| Prop | Type | Description |
| --- | --- | --- |
| colSpan | `number` |  |
### TableErrorProps

| Prop | Type | Description |
| --- | --- | --- |
| colSpan | `number` |  |
| title? | `string` | Default `Could not load this list`. |
| action? | `ReactNode` | A retry Button. |
### TableLoadingProps

| Prop | Type | Description |
| --- | --- | --- |
| columns | `number` |  |
| rows? | `number` | Default `4`. |
Source: https://github.com/baronunread/ikada/blob/main/src/components/table.tsx
