A semantic table in a scroll container, with sortable headers, loading, empty and error rows.
2.9 kB gzipSource Registry JSON Copies this page as Markdown# 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
Basic
Name
Region
Status
robin
eu-west
Live
wren
us-east
Live
finch
eu-west
Building
heron
us-east
Paused
<Tablelabel="Sprouts"><TableHeader><TableRow><TableHead>Name</TableHead><TableHead>Region</TableHead><TableHead>Status</TableHead></TableRow></TableHeader><TableBody><TableRow><TableCell>robin</TableCell><TableCell>eu-west</TableCell><TableCell><StatusDottone="success" live label="Live"/></TableCell></TableRow><TableRow><TableCell>wren</TableCell><TableCell>us-east</TableCell><TableCell><StatusDottone="success" live label="Live"/></TableCell></TableRow><TableRow><TableCell>finch</TableCell><TableCell>eu-west</TableCell><TableCell><StatusDottone="info" pulse label="Building"/></TableCell></TableRow><TableRow><TableCell>heron</TableCell><TableCell>us-east</TableCell><TableCell><StatusDottone="neutral"label="Paused"/></TableCell></TableRow></TableBody></Table>
The CLI writes components/ikada/table.tsx and merges the ikada theme into your CSS. It also installs the components it is built from: Skeleton. Or take it from the package: import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell, TableEmpty, TableError, TableLoading } from "@sproutboat/ikada";
Usage
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell, TableEmpty, TableError, TableLoading } from"@/components/ikada/table";
<Tablelabel="Sprouts"><TableHeader><TableRow><TableHead>Name</TableHead><TableHead>Region</TableHead><TableHead>Status</TableHead></TableRow></TableHeader><TableBody><TableRow><TableCell>robin</TableCell><TableCell>eu-west</TableCell><TableCell><StatusDottone="success" live label="Live"/></TableCell></TableRow><TableRow><TableCell>wren</TableCell><TableCell>us-east</TableCell><TableCell><StatusDottone="success" live label="Live"/></TableCell></TableRow><TableRow><TableCell>finch</TableCell><TableCell>eu-west</TableCell><TableCell><StatusDottone="info" pulse label="Building"/></TableCell></TableRow><TableRow><TableCell>heron</TableCell><TableCell>us-east</TableCell><TableCell><StatusDottone="neutral"label="Paused"/></TableCell></TableRow></TableBody></Table>
Examples
Basic
Wide tables scroll inside their container, never the page. 48px rows.
Name
Region
Status
robin
eu-west
Live
wren
us-east
Live
finch
eu-west
Building
heron
us-east
Paused
<Tablelabel="Sprouts"><TableHeader><TableRow><TableHead>Name</TableHead><TableHead>Region</TableHead><TableHead>Status</TableHead></TableRow></TableHeader><TableBody><TableRow><TableCell>robin</TableCell><TableCell>eu-west</TableCell><TableCell><StatusDottone="success" live label="Live"/></TableCell></TableRow><TableRow><TableCell>wren</TableCell><TableCell>us-east</TableCell><TableCell><StatusDottone="success" live label="Live"/></TableCell></TableRow><TableRow><TableCell>finch</TableCell><TableCell>eu-west</TableCell><TableCell><StatusDottone="info" pulse label="Building"/></TableCell></TableRow><TableRow><TableCell>heron</TableCell><TableCell>us-east</TableCell><TableCell><StatusDottone="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.
<Tablelabel="Sprouts"><TableHeader><TableRow><TableHead>Name</TableHead><TableHead>Region</TableHead></TableRow></TableHeader><TableBody><TableEmptycolSpan={2}>No sprouts yet. Plant your first one.</TableEmpty></TableBody></Table>
Error
Name
Region
Could not load this list
The API did not answer in time.
<Tablelabel="Sprouts"><TableHeader><TableRow><TableHead>Name</TableHead><TableHead>Region</TableHead></TableRow></TableHeader><TableBody><TableErrorcolSpan={2} action={<Buttonsize="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
Prop
Type
Description
labelrequired
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.
Also takes every prop of ComponentProps<"table">.
TableRowProps
Prop
Type
Description
selected
boolean
Highlights the row and sets aria-selected. Pair with a checkbox cell: that is the control. Default `false`.
Also takes every prop of ComponentProps<"tr">.
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
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.
Also takes every prop of Omit<ComponentProps<"th">, "onClick" | "aria-sort">, HideBelowProps, NumericProps.
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.