---
title: Table
description: "A semantic table component for displaying tabular data with row variants, selection cells, and sticky columns."
order: 31
tags: [components]
---

import { Preview } from "$lib/components/preview";
import { Badge, Table } from "areia";

# Table

A semantic table component for displaying tabular data with row variants, selection cells, and sticky columns.

[Source Code](https://github.com/ilhajs/areia/blob/main/packages/areia/src/components/table/index.ts)

Areia's `Table` renders platform table elements (`table`, `thead`, `tbody`, `tr`, `th`, `td`) with Areia styling and compound helpers for headers, cells, sticky columns, resize handles, and checkbox selection cells.

<Preview
  code={
    'import ilha from "ilha";\nimport { Table } from "areia";\n\nconst rows = [\n  {\n    subject: "Welcome to Areia",\n    from: "team@example.com",\n    date: "Apr 12",\n  },\n  {\n    subject: "Build complete",\n    from: "ci@example.com",\n    date: "Apr 11",\n  },\n  {\n    subject: "New comment",\n    from: "noreply@example.com",\n    date: "Apr 10",\n  },\n];\n\nexport default ilha.render(() => (\n  <Table>\n    <Table.Header>\n      <Table.Row>\n        <Table.Head>Subject</Table.Head>\n        <Table.Head>From</Table.Head>\n        <Table.Head>Date</Table.Head>\n      </Table.Row>\n    </Table.Header>\n    <Table.Body>\n      {rows.map((row) => (\n        <Table.Row>\n          <Table.Cell>{row.subject}</Table.Cell>\n          <Table.Cell>{row.from}</Table.Cell>\n          <Table.Cell>{row.date}</Table.Cell>\n        </Table.Row>\n      ))}\n    </Table.Body>\n  </Table>\n));'
  }
  lang="tsx"
>
  <Table>
    <Table.Header>
      <Table.Row>
        <Table.Head>Subject</Table.Head>
        <Table.Head>From</Table.Head>
        <Table.Head>Date</Table.Head>
      </Table.Row>
    </Table.Header>
    <Table.Body>
      <Table.Row>
        <Table.Cell>Welcome to Areia</Table.Cell>
        <Table.Cell>team@example.com</Table.Cell>
        <Table.Cell>Apr 12</Table.Cell>
      </Table.Row>
      <Table.Row>
        <Table.Cell>Build complete</Table.Cell>
        <Table.Cell>ci@example.com</Table.Cell>
        <Table.Cell>Apr 11</Table.Cell>
      </Table.Row>
      <Table.Row>
        <Table.Cell>New comment</Table.Cell>
        <Table.Cell>noreply@example.com</Table.Cell>
        <Table.Cell>Apr 10</Table.Cell>
      </Table.Row>
    </Table.Body>
  </Table>
</Preview>

## Import

```ts
import { Table } from "areia";
```

## Usage

<Preview
  code={
    'import ilha from "ilha";\nimport { Table } from "areia";\n\nexport default ilha.render(() => (\n  <Table>\n    <Table.Header>\n      <Table.Row>\n        <Table.Head>Name</Table.Head>\n        <Table.Head>Email</Table.Head>\n        <Table.Head>Role</Table.Head>\n      </Table.Row>\n    </Table.Header>\n    <Table.Body>\n      <Table.Row>\n        <Table.Cell>John Doe</Table.Cell>\n        <Table.Cell>john@example.com</Table.Cell>\n        <Table.Cell>Admin</Table.Cell>\n      </Table.Row>\n    </Table.Body>\n  </Table>\n));'
  }
  lang="tsx"
>
  <Table>
    <Table.Header>
      <Table.Row>
        <Table.Head>Name</Table.Head>
        <Table.Head>Email</Table.Head>
        <Table.Head>Role</Table.Head>
      </Table.Row>
    </Table.Header>
    <Table.Body>
      <Table.Row>
        <Table.Cell>John Doe</Table.Cell>
        <Table.Cell>john@example.com</Table.Cell>
        <Table.Cell>Admin</Table.Cell>
      </Table.Row>
    </Table.Body>
  </Table>
</Preview>

## Examples

### With Checkboxes

Add selection columns with `Table.CheckHead` and `Table.CheckCell`. They render Areia checkboxes with larger cell hit targets.

<Preview
  code={
    'import ilha from "ilha";\nimport { Table } from "areia";\n\nconst rows = [\n  {\n    id: "1",\n    subject: "Welcome to Areia",\n    from: "team@example.com",\n    date: "Apr 12",\n  },\n  {\n    id: "2",\n    subject: "Build complete",\n    from: "ci@example.com",\n    date: "Apr 11",\n  },\n  {\n    id: "3",\n    subject: "New comment",\n    from: "noreply@example.com",\n    date: "Apr 10",\n  },\n];\n\nconst selectedIds = new Set(["2"]);\n\nexport default ilha.render(() => (\n  <Table>\n    <Table.Header>\n      <Table.Row>\n        <Table.CheckHead\n          checked={selectedIds.size === rows.length}\n          indeterminate={\n            selectedIds.size > 0 &&\n            selectedIds.size < rows.length\n          }\n          label="Select all rows"\n        />\n        <Table.Head>Subject</Table.Head>\n        <Table.Head>From</Table.Head>\n        <Table.Head>Date</Table.Head>\n      </Table.Row>\n    </Table.Header>\n    <Table.Body>\n      {rows.map((row) => (\n        <Table.Row\n          variant={\n            selectedIds.has(row.id) ? "selected" : "default"\n          }\n        >\n          <Table.CheckCell\n            checked={selectedIds.has(row.id)}\n            label={`Select ${row.subject}`}\n            value={row.id}\n          />\n          <Table.Cell>{row.subject}</Table.Cell>\n          <Table.Cell>{row.from}</Table.Cell>\n          <Table.Cell>{row.date}</Table.Cell>\n        </Table.Row>\n      ))}\n    </Table.Body>\n  </Table>\n));'
  }
  lang="tsx"
  codeOnly
/>

### Compact Header

Use `variant="compact"` on `Table.Header` for a more condensed header style.

<Preview
  code={
    'import ilha from "ilha";\nimport { Table } from "areia";\n\nconst rows = [\n  {\n    subject: "Welcome to Areia",\n    from: "team@example.com",\n    date: "Apr 12",\n  },\n  {\n    subject: "Build complete",\n    from: "ci@example.com",\n    date: "Apr 11",\n  },\n  {\n    subject: "New comment",\n    from: "noreply@example.com",\n    date: "Apr 10",\n  },\n];\n\nexport default ilha.render(() => (\n  <Table>\n    <Table.Header variant="compact">\n      <Table.Row>\n        <Table.Head>Subject</Table.Head>\n        <Table.Head>From</Table.Head>\n        <Table.Head>Date</Table.Head>\n      </Table.Row>\n    </Table.Header>\n    <Table.Body>\n      {rows.map((row) => (\n        <Table.Row>\n          <Table.Cell>{row.subject}</Table.Cell>\n          <Table.Cell>{row.from}</Table.Cell>\n          <Table.Cell>{row.date}</Table.Cell>\n        </Table.Row>\n      ))}\n    </Table.Body>\n  </Table>\n));'
  }
  lang="tsx"
>
  <Table>
    <Table.Header variant="compact">
      <Table.Row>
        <Table.Head>Subject</Table.Head>
        <Table.Head>From</Table.Head>
        <Table.Head>Date</Table.Head>
      </Table.Row>
    </Table.Header>
    <Table.Body>
      <Table.Row>
        <Table.Cell>Welcome to Areia</Table.Cell>
        <Table.Cell>team@example.com</Table.Cell>
        <Table.Cell>Apr 12</Table.Cell>
      </Table.Row>
      <Table.Row>
        <Table.Cell>Build complete</Table.Cell>
        <Table.Cell>ci@example.com</Table.Cell>
        <Table.Cell>Apr 11</Table.Cell>
      </Table.Row>
      <Table.Row>
        <Table.Cell>New comment</Table.Cell>
        <Table.Cell>noreply@example.com</Table.Cell>
        <Table.Cell>Apr 10</Table.Cell>
      </Table.Row>
    </Table.Body>
  </Table>
</Preview>

### Selected Row

Use `variant="selected"` on `Table.Row` to highlight selected rows.

<Preview
  code={
    'import ilha from "ilha";\nimport { Table } from "areia";\n\nconst rows = [\n  {\n    id: "1",\n    subject: "Welcome to Areia",\n    from: "team@example.com",\n    date: "Apr 12",\n  },\n  {\n    id: "2",\n    subject: "Build complete",\n    from: "ci@example.com",\n    date: "Apr 11",\n  },\n  {\n    id: "3",\n    subject: "New comment",\n    from: "noreply@example.com",\n    date: "Apr 10",\n  },\n];\n\nexport default ilha.render(() => (\n  <Table>\n    <Table.Header>\n      <Table.Row>\n        <Table.Head>Subject</Table.Head>\n        <Table.Head>From</Table.Head>\n        <Table.Head>Date</Table.Head>\n      </Table.Row>\n    </Table.Header>\n    <Table.Body>\n      {rows.map((row) => (\n        <Table.Row\n          variant={row.id === "2" ? "selected" : "default"}\n        >\n          <Table.Cell>{row.subject}</Table.Cell>\n          <Table.Cell>{row.from}</Table.Cell>\n          <Table.Cell>{row.date}</Table.Cell>\n        </Table.Row>\n      ))}\n    </Table.Body>\n  </Table>\n));'
  }
  lang="tsx"
  codeOnly
/>

### Fixed Layout with Column Sizes

For precise control over column widths, set `layout="fixed"` and provide a `colgroup`.

<Preview
  code={
    'import ilha from "ilha";\nimport { Table } from "areia";\n\nconst rows = [\n  {\n    subject: "Welcome to Areia",\n    from: "team@example.com",\n    date: "Apr 12",\n  },\n  {\n    subject: "Build complete",\n    from: "ci@example.com",\n    date: "Apr 11",\n  },\n  {\n    subject: "New comment",\n    from: "noreply@example.com",\n    date: "Apr 10",\n  },\n];\n\nexport default ilha.render(() => (\n  <Table layout="fixed">\n    <colgroup>\n      <col class="w-1/2" />\n      <col class="w-1/4" />\n      <col class="w-1/4" />\n    </colgroup>\n    <Table.Header>\n      <Table.Row>\n        <Table.Head>Subject</Table.Head>\n        <Table.Head>From</Table.Head>\n        <Table.Head>Date</Table.Head>\n      </Table.Row>\n    </Table.Header>\n    <Table.Body>\n      {rows.map((row) => (\n        <Table.Row>\n          <Table.Cell>{row.subject}</Table.Cell>\n          <Table.Cell>{row.from}</Table.Cell>\n          <Table.Cell>{row.date}</Table.Cell>\n        </Table.Row>\n      ))}\n    </Table.Body>\n  </Table>\n));'
  }
  lang="tsx"
>
  <Table layout="fixed">
    <colgroup>
      <col class="w-1/2" />
      <col class="w-1/4" />
      <col class="w-1/4" />
    </colgroup>
    <Table.Header>
      <Table.Row>
        <Table.Head>Subject</Table.Head>
        <Table.Head>From</Table.Head>
        <Table.Head>Date</Table.Head>
      </Table.Row>
    </Table.Header>
    <Table.Body>
      <Table.Row>
        <Table.Cell>Welcome to Areia</Table.Cell>
        <Table.Cell>team@example.com</Table.Cell>
        <Table.Cell>Apr 12</Table.Cell>
      </Table.Row>
      <Table.Row>
        <Table.Cell>Build complete</Table.Cell>
        <Table.Cell>ci@example.com</Table.Cell>
        <Table.Cell>Apr 11</Table.Cell>
      </Table.Row>
      <Table.Row>
        <Table.Cell>New comment</Table.Cell>
        <Table.Cell>noreply@example.com</Table.Cell>
        <Table.Cell>Apr 10</Table.Cell>
      </Table.Row>
    </Table.Body>
  </Table>
</Preview>

### Sticky Column

Pin a column to the left or right edge of a horizontal scroll container with `sticky="left"` or `sticky="right"` on `Table.Head` and `Table.Cell`.

<Preview
  code={
    'import ilha from "ilha";\nimport { Badge, Table } from "areia";\n\nconst rows = [\n  {\n    subject: "Welcome to Areia",\n    from: "team@example.com",\n    date: "Apr 12",\n    tags: ["docs"],\n  },\n  {\n    subject: "Build complete",\n    from: "ci@example.com",\n    date: "Apr 11",\n    tags: ["ci", "deploy"],\n  },\n  {\n    subject: "New comment",\n    from: "noreply@example.com",\n    date: "Apr 10",\n    tags: ["inbox"],\n  },\n];\n\nexport default ilha.render(() => (\n  <div class="w-full overflow-x-auto">\n    <Table class="min-w-[720px]">\n      <Table.Header>\n        <Table.Row>\n          <Table.Head>Subject</Table.Head>\n          <Table.Head>From</Table.Head>\n          <Table.Head>Date</Table.Head>\n          <Table.Head>Tags</Table.Head>\n          <Table.Head sticky="right">Actions</Table.Head>\n        </Table.Row>\n      </Table.Header>\n      <Table.Body>\n        {rows.map((row) => (\n          <Table.Row>\n            <Table.Cell>{row.subject}</Table.Cell>\n            <Table.Cell>{row.from}</Table.Cell>\n            <Table.Cell>{row.date}</Table.Cell>\n            <Table.Cell>\n              <div class="flex gap-1">\n                {row.tags.map((tag) => (\n                  <Badge variant="neutral">{tag}</Badge>\n                ))}\n              </div>\n            </Table.Cell>\n            <Table.Cell sticky="right">\n              <button class="text-sm underline">View</button>\n            </Table.Cell>\n          </Table.Row>\n        ))}\n      </Table.Body>\n    </Table>\n  </div>\n));'
  }
  lang="tsx"
  codeOnly
/>

### Sticky Header

Set `sticky` on `Table.Header` and put the table in a height-constrained vertical scroll container.

<Preview
  code={
    'import ilha from "ilha";\nimport { Table } from "areia";\n\nconst rows = Array.from({ length: 12 }, (_, index) => ({\n  name: `Component ${index + 1}`,\n  status: index % 2 === 0 ? "Stable" : "Draft",\n}));\n\nexport default ilha.render(() => (\n  <div class="max-h-64 overflow-y-auto">\n    <Table>\n      <Table.Header sticky>\n        <Table.Row>\n          <Table.Head>Name</Table.Head>\n          <Table.Head>Status</Table.Head>\n        </Table.Row>\n      </Table.Header>\n      <Table.Body>\n        {rows.map((row) => (\n          <Table.Row>\n            <Table.Cell>{row.name}</Table.Cell>\n            <Table.Cell>{row.status}</Table.Cell>\n          </Table.Row>\n        ))}\n      </Table.Body>\n    </Table>\n  </div>\n));'
  }
  lang="tsx"
  codeOnly
/>

### Resize Handle

`Table.ResizeHandle` resizes its containing `th` or `td` by updating the cell width while you drag it. Place it inside a relatively positioned cell.

<Preview
  code={
    'import ilha from "ilha";\nimport { Table } from "areia";\n\nexport default ilha.render(() => (\n  <Table>\n    <Table.Header>\n      <Table.Row>\n        <Table.Head class="relative">\n          Name\n          <Table.ResizeHandle />\n        </Table.Head>\n        <Table.Head>Role</Table.Head>\n      </Table.Row>\n    </Table.Header>\n  </Table>\n));'
  }
  lang="tsx"
>
  <Table>
    <Table.Header>
      <Table.Row>
        <Table.Head class="relative">
          Name
          <Table.ResizeHandle />
        </Table.Head>
        <Table.Head>Role</Table.Head>
      </Table.Row>
    </Table.Header>
  </Table>
</Preview>

## API Reference

### Table

Root table component. Renders a semantic `<table>` element.

| Prop        | Type                | Default  | Description                                    |
| ----------- | ------------------- | -------- | ---------------------------------------------- |
| `layout`    | `"auto" \| "fixed"` | `"auto"` | Table layout algorithm.                        |
| `children`  | `unknown`           | -        | Table sections, rows, and optional `colgroup`. |
| `class`     | `string`            | -        | Additional CSS classes applied to the table.   |
| `className` | `string`            | -        | Alias for `class`.                             |

### Table.Header

Table header section. Renders `<thead>`.

| Prop        | Type                     | Default     | Description                                                 |
| ----------- | ------------------------ | ----------- | ----------------------------------------------------------- |
| `variant`   | `"default" \| "compact"` | `"default"` | Header style.                                               |
| `sticky`    | `boolean`                | -           | Make header cells stick to the top of the scroll container. |
| `children`  | `unknown`                | -           | Header rows.                                                |
| `class`     | `string`                 | -           | Additional CSS classes.                                     |
| `className` | `string`                 | -           | Alias for `class`.                                          |

### Table.Row

Table row. Supports `variant: "selected"` for highlighting.

| Prop        | Type                      | Default     | Description             |
| ----------- | ------------------------- | ----------- | ----------------------- |
| `variant`   | `"default" \| "selected"` | `"default"` | Row visual variant.     |
| `children`  | `unknown`                 | -           | Row cells.              |
| `class`     | `string`                  | -           | Additional CSS classes. |
| `className` | `string`                  | -           | Alias for `class`.      |

### Table.Head

Header cell. Renders `<th>`.

| Prop        | Type                | Default | Description                       |
| ----------- | ------------------- | ------- | --------------------------------- |
| `sticky`    | `"left" \| "right"` | -       | Pin the header cell horizontally. |
| `children`  | `unknown`           | -       | Cell content.                     |
| `class`     | `string`            | -       | Additional CSS classes.           |
| `className` | `string`            | -       | Alias for `class`.                |

### Table.Cell

Body cell. Renders `<td>`.

| Prop        | Type                | Default | Description                     |
| ----------- | ------------------- | ------- | ------------------------------- |
| `sticky`    | `"left" \| "right"` | -       | Pin the body cell horizontally. |
| `children`  | `unknown`           | -       | Cell content.                   |
| `class`     | `string`            | -       | Additional CSS classes.         |
| `className` | `string`            | -       | Alias for `class`.              |

### Table.Body and Table.Footer

`Table.Body` renders `<tbody>`. `Table.Footer` renders `<tfoot>`. Both accept standard table section attributes plus `children`, `class`, and `className`.

### Table.CheckHead and Table.CheckCell

Selection cells with Areia checkboxes.

| Prop            | Type      | Default | Description                             |
| --------------- | --------- | ------- | --------------------------------------- |
| `checked`       | `boolean` | -       | Whether the checkbox is checked.        |
| `indeterminate` | `boolean` | -       | Whether the checkbox is visually mixed. |
| `label`         | `string`  | -       | Accessible label.                       |
| `disabled`      | `boolean` | -       | Whether the checkbox is disabled.       |
| `name`          | `string`  | -       | Native checkbox name.                   |
| `value`         | `string`  | -       | Native checkbox value.                  |

### Table.ResizeHandle

Handle for column resizing. Dragging it updates the containing `th` or `td` width inline.

| Prop        | Type     | Default | Description                                  |
| ----------- | -------- | ------- | -------------------------------------------- |
| `minWidth`  | `number` | `40`    | Minimum cell width in pixels while dragging. |
| `class`     | `string` | -       | Additional CSS classes.                      |
| `className` | `string` | -       | Alias for `class`.                           |

## TanStack Table Integration

For advanced features like sorting, filtering, and resizable columns, integrate with [TanStack Table](https://tanstack.com/table). Areia's `Table` is semantic and unopinionated, so you can render TanStack header groups, rows, and cells through the same compound helpers.

```ts
import { Table } from "areia";

// Pseudo-code: use your TanStack table instance to render headers and cells.
const markup = Table({
  children: [
    Table.Header({ children: "...header groups" }),
    Table.Body({ children: "...row model" }),
  ],
});
```

## Accessibility

### Semantic HTML

`Table` uses semantic `<table>`, `<thead>`, `<tbody>`, `<tfoot>`, `<tr>`, `<th>`, and `<td>` elements for screen reader navigation.

### Checkbox Labels

Always provide `label` for `Table.CheckHead` and `Table.CheckCell` so the generated checkboxes have useful accessible names.

### Keyboard Navigation

Tab moves focus through interactive elements. Checkboxes respond to Space using native checkbox behavior.
