Skip to content

Table

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

Source Code

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.

Subject From Date
Welcome to Areia team@example.com Apr 12
Build complete ci@example.com Apr 11
New comment noreply@example.com Apr 10
import ilha from "ilha";
import { Table } from "areia";

const rows = [
  {
    subject: "Welcome to Areia",
    from: "team@example.com",
    date: "Apr 12",
  },
  {
    subject: "Build complete",
    from: "ci@example.com",
    date: "Apr 11",
  },
  {
    subject: "New comment",
    from: "noreply@example.com",
    date: "Apr 10",
  },
];

export default ilha.render(() => (
  <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>
      {rows.map((row) => (
        <Table.Row>
          <Table.Cell>{row.subject}</Table.Cell>
          <Table.Cell>{row.from}</Table.Cell>
          <Table.Cell>{row.date}</Table.Cell>
        </Table.Row>
      ))}
    </Table.Body>
  </Table>
));

Import

import { Table } from "areia";

Usage

Name Email Role
John Doe john@example.com Admin
import ilha from "ilha";
import { Table } from "areia";

export default ilha.render(() => (
  <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>
));

Examples

With Checkboxes

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

import ilha from "ilha";
import { Table } from "areia";

const rows = [
  {
    id: "1",
    subject: "Welcome to Areia",
    from: "team@example.com",
    date: "Apr 12",
  },
  {
    id: "2",
    subject: "Build complete",
    from: "ci@example.com",
    date: "Apr 11",
  },
  {
    id: "3",
    subject: "New comment",
    from: "noreply@example.com",
    date: "Apr 10",
  },
];

const selectedIds = new Set(["2"]);

export default ilha.render(() => (
  <Table>
    <Table.Header>
      <Table.Row>
        <Table.CheckHead
          checked={selectedIds.size === rows.length}
          indeterminate={
            selectedIds.size > 0 &&
            selectedIds.size < rows.length
          }
          label="Select all rows"
        />
        <Table.Head>Subject</Table.Head>
        <Table.Head>From</Table.Head>
        <Table.Head>Date</Table.Head>
      </Table.Row>
    </Table.Header>
    <Table.Body>
      {rows.map((row) => (
        <Table.Row
          variant={
            selectedIds.has(row.id) ? "selected" : "default"
          }
        >
          <Table.CheckCell
            checked={selectedIds.has(row.id)}
            label={`Select ${row.subject}`}
            value={row.id}
          />
          <Table.Cell>{row.subject}</Table.Cell>
          <Table.Cell>{row.from}</Table.Cell>
          <Table.Cell>{row.date}</Table.Cell>
        </Table.Row>
      ))}
    </Table.Body>
  </Table>
));

Compact Header

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

Subject From Date
Welcome to Areia team@example.com Apr 12
Build complete ci@example.com Apr 11
New comment noreply@example.com Apr 10
import ilha from "ilha";
import { Table } from "areia";

const rows = [
  {
    subject: "Welcome to Areia",
    from: "team@example.com",
    date: "Apr 12",
  },
  {
    subject: "Build complete",
    from: "ci@example.com",
    date: "Apr 11",
  },
  {
    subject: "New comment",
    from: "noreply@example.com",
    date: "Apr 10",
  },
];

export default ilha.render(() => (
  <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>
      {rows.map((row) => (
        <Table.Row>
          <Table.Cell>{row.subject}</Table.Cell>
          <Table.Cell>{row.from}</Table.Cell>
          <Table.Cell>{row.date}</Table.Cell>
        </Table.Row>
      ))}
    </Table.Body>
  </Table>
));

Selected Row

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

import ilha from "ilha";
import { Table } from "areia";

const rows = [
  {
    id: "1",
    subject: "Welcome to Areia",
    from: "team@example.com",
    date: "Apr 12",
  },
  {
    id: "2",
    subject: "Build complete",
    from: "ci@example.com",
    date: "Apr 11",
  },
  {
    id: "3",
    subject: "New comment",
    from: "noreply@example.com",
    date: "Apr 10",
  },
];

export default ilha.render(() => (
  <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>
      {rows.map((row) => (
        <Table.Row
          variant={row.id === "2" ? "selected" : "default"}
        >
          <Table.Cell>{row.subject}</Table.Cell>
          <Table.Cell>{row.from}</Table.Cell>
          <Table.Cell>{row.date}</Table.Cell>
        </Table.Row>
      ))}
    </Table.Body>
  </Table>
));

Fixed Layout with Column Sizes

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

Subject From Date
Welcome to Areia team@example.com Apr 12
Build complete ci@example.com Apr 11
New comment noreply@example.com Apr 10
import ilha from "ilha";
import { Table } from "areia";

const rows = [
  {
    subject: "Welcome to Areia",
    from: "team@example.com",
    date: "Apr 12",
  },
  {
    subject: "Build complete",
    from: "ci@example.com",
    date: "Apr 11",
  },
  {
    subject: "New comment",
    from: "noreply@example.com",
    date: "Apr 10",
  },
];

export default ilha.render(() => (
  <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>
      {rows.map((row) => (
        <Table.Row>
          <Table.Cell>{row.subject}</Table.Cell>
          <Table.Cell>{row.from}</Table.Cell>
          <Table.Cell>{row.date}</Table.Cell>
        </Table.Row>
      ))}
    </Table.Body>
  </Table>
));

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.

import ilha from "ilha";
import { Badge, Table } from "areia";

const rows = [
  {
    subject: "Welcome to Areia",
    from: "team@example.com",
    date: "Apr 12",
    tags: ["docs"],
  },
  {
    subject: "Build complete",
    from: "ci@example.com",
    date: "Apr 11",
    tags: ["ci", "deploy"],
  },
  {
    subject: "New comment",
    from: "noreply@example.com",
    date: "Apr 10",
    tags: ["inbox"],
  },
];

export default ilha.render(() => (
  <div class="w-full overflow-x-auto">
    <Table class="min-w-[720px]">
      <Table.Header>
        <Table.Row>
          <Table.Head>Subject</Table.Head>
          <Table.Head>From</Table.Head>
          <Table.Head>Date</Table.Head>
          <Table.Head>Tags</Table.Head>
          <Table.Head sticky="right">Actions</Table.Head>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        {rows.map((row) => (
          <Table.Row>
            <Table.Cell>{row.subject}</Table.Cell>
            <Table.Cell>{row.from}</Table.Cell>
            <Table.Cell>{row.date}</Table.Cell>
            <Table.Cell>
              <div class="flex gap-1">
                {row.tags.map((tag) => (
                  <Badge variant="neutral">{tag}</Badge>
                ))}
              </div>
            </Table.Cell>
            <Table.Cell sticky="right">
              <button class="text-sm underline">View</button>
            </Table.Cell>
          </Table.Row>
        ))}
      </Table.Body>
    </Table>
  </div>
));

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

import ilha from "ilha";
import { Table } from "areia";

const rows = Array.from({ length: 12 }, (_, index) => ({
  name: `Component ${index + 1}`,
  status: index % 2 === 0 ? "Stable" : "Draft",
}));

export default ilha.render(() => (
  <div class="max-h-64 overflow-y-auto">
    <Table>
      <Table.Header sticky>
        <Table.Row>
          <Table.Head>Name</Table.Head>
          <Table.Head>Status</Table.Head>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        {rows.map((row) => (
          <Table.Row>
            <Table.Cell>{row.name}</Table.Cell>
            <Table.Cell>{row.status}</Table.Cell>
          </Table.Row>
        ))}
      </Table.Body>
    </Table>
  </div>
));

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.

Name

Role
import ilha from "ilha";
import { Table } from "areia";

export default ilha.render(() => (
  <Table>
    <Table.Header>
      <Table.Row>
        <Table.Head class="relative">
          Name
          <Table.ResizeHandle />
        </Table.Head>
        <Table.Head>Role</Table.Head>
      </Table.Row>
    </Table.Header>
  </Table>
));

API Reference

Table

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

PropTypeDefaultDescription
layout"auto" | "fixed""auto"Table layout algorithm.
childrenunknown-Table sections, rows, and optional colgroup.
classstring-Additional CSS classes applied to the table.
classNamestring-Alias for class.

Table.Header

Table header section. Renders <thead>.

PropTypeDefaultDescription
variant"default" | "compact""default"Header style.
stickyboolean-Make header cells stick to the top of the scroll container.
childrenunknown-Header rows.
classstring-Additional CSS classes.
classNamestring-Alias for class.

Table.Row

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

PropTypeDefaultDescription
variant"default" | "selected""default"Row visual variant.
childrenunknown-Row cells.
classstring-Additional CSS classes.
classNamestring-Alias for class.

Table.Head

Header cell. Renders <th>.

PropTypeDefaultDescription
sticky"left" | "right"-Pin the header cell horizontally.
childrenunknown-Cell content.
classstring-Additional CSS classes.
classNamestring-Alias for class.

Table.Cell

Body cell. Renders <td>.

PropTypeDefaultDescription
sticky"left" | "right"-Pin the body cell horizontally.
childrenunknown-Cell content.
classstring-Additional CSS classes.
classNamestring-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.

PropTypeDefaultDescription
checkedboolean-Whether the checkbox is checked.
indeterminateboolean-Whether the checkbox is visually mixed.
labelstring-Accessible label.
disabledboolean-Whether the checkbox is disabled.
namestring-Native checkbox name.
valuestring-Native checkbox value.

Table.ResizeHandle

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

PropTypeDefaultDescription
minWidthnumber40Minimum cell width in pixels while dragging.
classstring-Additional CSS classes.
classNamestring-Alias for class.

TanStack Table Integration

For advanced features like sorting, filtering, and resizable columns, integrate with TanStack Table. Areia's Table is semantic and unopinionated, so you can render TanStack header groups, rows, and cells through the same compound helpers.

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.