# Table

> A styled, horizontally-scrollable table. Write plain thead/tbody markup — Table only styles it.

URL: https://apogee.avaui.dev/docs/table
Last updated: 2026-10-15

A styled, horizontally-scrollable table. Write plain thead/tbody markup — Table only styles it.

Last updated October 15, 2026

Uses JavaScript

Fades the hidden edge while the table scrolls sideways and makes the scrolling wrapper a tab stop only while it overflows. With `sortable` or `selectable` the same island adds sort buttons and wires the row checkboxes.

**Without JS:** The table still scrolls sideways and shows its rows in source order; sort buttons and select-all do not appear.

```astro
<Table>
<thead>
  <tr>
    <th>Name</th>
    <th>Role</th>
    <th>Status</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>Ada Lovelace</td>
    <td>Engineer</td>
    <td>Active</td>
  </tr>
  <tr>
    <td>Grace Hopper</td>
    <td>Engineer</td>
    <td>Active</td>
  </tr>
</tbody>
</Table>
```

## Add

```sh
npx @ava-ui/apogee add table
```

Depends on `utils` (`fade.ts`, `table-enhance.ts`). Uses `@lucide/astro` for the sort chevrons.

## Usage

```astro
---
import Table from '@/components/apogee/Table/Table.astro'
---
<Table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Role</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Ada Lovelace</td>
      <td>Engineer</td>
    </tr>
  </tbody>
</Table>
```

## Anatomy

Table has no sub-components or named slots — it styles the real table elements you write, via descendant selectors scoped to an internal `.apogee-table` class:

-   **Default slot** — write a plain `<thead>` / `<tbody>` (and optional `<tfoot>`) with `<tr>` / `<th>` / `<td>` inside, exactly as you would in raw HTML.
-   **`<th>`** — gets padding and semibold weight; a `<thead>`’s `<th>` additionally gets the bottom border rule that separates it from the body.
-   **`<td>`** — gets padding and a top border rule, which reads as the row separator.
-   Every cell (`th` and `td`) is start-aligned by default — including `<th>`, overriding the browser’s default centering — via a `@layer components` rule, so a `text-end` / `text-center` utility on any individual cell still wins.
-   The whole table is wrapped in an `overflow-x-auto` `<div>` automatically, so wide tables scroll horizontally instead of breaking the page layout — no prop needed.

## API

`@composable` — exposed to Content Editors in AVA. Any other attribute passes through to the `<table>` element (not the scroll wrapper `<div>`).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `headerStyle` | `'normal' \| 'caps'` | `'normal'` | Header text: sentence case and semibold, or small caps. |
| `border` | `boolean` | `false` | Outer border with rounded corners. |
| `columnBorder` | `boolean` | `false` | Hairline between columns. |
| `size` | `'small' \| 'medium'` | `'small'` | Cell padding. |
| `striped` | `boolean` | `false` | Alternate row fills. |
| `headerFill` | `boolean` | `true` | A faded fill behind the header row, matching DataGrid. |
| `sortable` | `boolean` | `false` | Every header with text becomes a sort button. A cell’s `data-value` is its sort key (use it when the cell holds an avatar or badge); `data-sortable="false"` on a header skips it, and a header holding a control (a checkbox, a link) is never wrapped. |
| `selectable` | `boolean` | `false` | Wires `<Checkbox data-row>` cells and one `<Checkbox data-all>` (indeterminate when some rows are checked) and tints checked rows. |
| `class` | `string` | — | Extra classes on the `<table>`, merged last. |

## Borders and overflow

The wrapper scrolls sideways and fades the hidden edge.

```astro
<Table border columnBorder>…</Table>
<div class="max-w-md"><Table>…12 columns…</Table></div>
```

## Sorting and selection

Rich cells and sorting in one table. The name cell holds an avatar, so it carries `data-value` as its sort key; the checkbox header has no text and is skipped.

```astro
<Table sortable selectable>
<thead><tr>
  <th><Checkbox data-all aria-label="Select all" /></th>
  <th>Name</th><th>Status</th><th class="text-end">Projects</th>
</tr></thead>
<tbody>
  <tr>
    <td><Checkbox data-row aria-label="Select Ana Lima" /></td>
    <td data-value="Ana Lima"><Avatar name="Ana Lima" size="xsmall" /> Ana Lima</td>
    <td><Badge size="small" color="positive">Active</Badge></td>
    <td class="text-end">12</td>
  </tr>
  …
</tbody>
</Table>
```

## Density and stripes

Both `size` values, `striped`, and `headerFill={false}`:

```astro
<Table size="small">…</Table>
<Table size="medium">…</Table>
<Table striped>…</Table>
<Table headerFill={false}>…</Table>
```

## States

Hover a row in the live preview above — `tbody tr:hover` gets a `bg-neutral-faded` fill.

## Examples

Small-caps headers:

```astro
<Table headerStyle="caps">…</Table>
```

End-align a numeric column:

```astro
<Table>
<thead>
  <tr>
    <th>Item</th>
    <th class="text-end">Price</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>Widget</td>
    <td class="text-end">$12.00</td>
  </tr>
  <tr>
    <td>Gadget</td>
    <td class="text-end">$34.50</td>
  </tr>
</tbody>
</Table>
```

A table card: Card regions hold the title row and the pagination footer.

```astro
<Card padding="none">
<SectionHeader slot="header" size="small" heading="Members" description="Everyone with access." class="flex-1" />
<Button slot="header" size="small">Invite</Button>
<Table>…</Table>
<Pagination slot="footer" currentPage={1} totalPages={8} class="ms-auto" />
</Card>
```

A captioned table (caption hidden visually, still announced):

```astro
<Table>
  <caption class="sr-only">Team members</caption>
  <thead>
    <tr><th scope="col">Name</th><th scope="col">Role</th></tr>
  </thead>
  <tbody>
    <tr><td>Ada Lovelace</td><td>Engineer</td></tr>
  </tbody>
</Table>
```

## Accessibility

-   Real `<table>` / `<thead>` / `<tbody>` / `<tr>` / `<th>` / `<td>` — full table semantics for free; Table only adds styling.
-   Give every `<th>` a `scope="col"` (column header) or `scope="row"` (row header) — Table styles headers but doesn’t add `scope` for you.
-   Add a `<caption>` (visually hidden with `sr-only` if you don’t want it shown) to name the table for screen readers.
-   For a wide table relying on the horizontal-scroll wrapper, consider adding `tabindex="0"` and a `role="region"` with an `aria-label` on the scroll container if it needs to be reachable and announced as its own scrollable region.

## Tokens

| Token | Used for |
| --- | --- |
| `--foreground-neutral` | Header + cell text |
| `--border-neutral` | Header bottom rule + row top rule |
| `--background-neutral-faded` | Row hover, header fill, stripes, checked rows |

## Do / Don’t

-   **Do** write real `<th scope="col">` headers — Table styles them but doesn’t add `scope` for you.
-   **Do** end-align numeric columns (`text-end` on the `<td>`/`<th>`) — the default is start-aligned.
-   **Don’t** put interactive controls (buttons, inputs) in cells without their own visible focus state — Table doesn’t style them.
-   **Don’t** rely on horizontal scroll alone for a critical column — put the most important column first (reading order follows source order, and mirrors under RTL).

## Content

-   Header labels are short nouns, sentence case, no ending punctuation.
-   Truncate long cell text with an explicit `title` attribute holding the full value, not silent CSS overflow with no fallback.
-   Show a neutral placeholder (“—”) for an empty cell rather than leaving it blank, so a value isn’t mistaken for missing data.

## Related

[

DataGridA data table with client-side column sorting and optional row selection.

](https://apogee.avaui.dev/docs/data-grid)[

ListA styled ordered or unordered list for grouped item content — not for layout rows.

](https://apogee.avaui.dev/docs/list)[

ScrollAreaA scroll container with a thin, token-coloured scrollbar.

](https://apogee.avaui.dev/docs/scroll-area)
