Table
A styled data table around native `<table>`: zebra, bordered, hover, sticky header, and a sortable-header affordance.
Live demo
Table dresses a native <table> in xoji styling without taking over its markup. The binding is a thin wrapper that relays consumer-authored rows: you write the real <thead>/<tbody>/<tr>/<th>/<td> and tag them with the xoji-table__* part classes, and the wrapper supplies the scroll container, variant, size, hover, and sticky behavior.
Variants default/striped/bordered set the row and cell chrome; compact/normal sizes set the cell padding. The sortable-header affordance renders a sort glyph and exposes an aria-sort hook on the header cell; the engine is presentation-only, so the consumer wires the actual sort and toggles aria-sort (ascending/descending/none), and the glyph rotates to match. scope belongs on every header cell so screen readers associate it with its row or column.
When to use
How this component composes with the rest of the set.
Props
5 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
Appearance
Variants
default
Plain rows separated by a bottom rule; no class needed.
striped
Alternating body-row backgrounds for scannability.
bordered
A full grid; every cell gets a border.
Sizes
normal
Default density.
compact
Tighter cell padding.
States
hover
Pointer over a body row (with hover); the row paints the hover tint.
sortable
A sortable header cell: pointer cursor and a hover tint behind the label.
sorted
A header cell with aria-sort set; the glyph colors and points up (asc) or down (desc).
focus-visible
Keyboard focus on a sortable header: an inset token ring, plus a transparent outline that becomes real in forced-colors mode.
Anatomy
The named parts that make up the component, with their selectors.
wrap
The horizontal-scroll container that clips the table to its width and rounds the corners.
table
The <table> itself: collapsed borders, surface background, base typography.
caption
An optional <caption> rendered above the table as a muted label.
head
The <thead> group; its last header row carries the heavier under-rule.
body
The <tbody> group that zebra striping and hover target.
row
A <tr>: the unit zebra striping and row hover apply to.
header-cell
A <th>: semibold ink on a tint, with scope and an optional sortable affordance.
cell
A <td>: the standard data cell with a row-separating bottom rule.
sort
The decorative sort glyph inside a sortable header; rotates and colors per aria-sort.
footer-cell
A <td>/<th> in <tfoot>: a summary cell on a tint above a heavy top rule.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
Live coverage check against the xoji-default register
(derive(xojiDefault, { anchors }) →
coverComponent(manifest, register)). Every token this component
consumes must be a key the algorithm produces.
--accent-text
--bg-1
--bg-2
--border-normal
--border-thick
--border-thin
--duration-fast
--ease-standard
--fg-0
--fg-1
--fg-2
--fg-3
--font-sans
--leading-normal
--line
--line-2
--radius-lg
--ring
--space-1
--space-2
--space-3
--space-4
--state-hover
--text-body
--text-sm
--weight-medium
--weight-semibold
Slots
The consumer-authored <table> (with xoji-table__* part classes on its rows and cells).
Accessibility
Code
Variants, sticky header, and a sortable column
Striped rows with hover and a sticky, sortable header; the consumer supplies rows and owns the sort.
<xoji-table variant="striped" hover sticky aria-label="Recent orders">
<table>
<caption class="xoji-table__caption">Recent orders</caption>
<thead class="xoji-table__head">
<tr class="xoji-table__row">
<th class="xoji-table__header-cell xoji-table__header-cell--sortable" scope="col" aria-sort="ascending">
<span class="xoji-table__header-content">Order
<span class="xoji-table__sort" aria-hidden="true">
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 8l5 6H7l5-6Z" /></svg>
</span>
</span>
</th>
<th class="xoji-table__header-cell" scope="col">Customer</th>
<th class="xoji-table__header-cell" scope="col">Total</th>
</tr>
</thead>
<tbody class="xoji-table__body">
<tr class="xoji-table__row">
<td class="xoji-table__cell">#1024</td>
<td class="xoji-table__cell">Ada Lovelace</td>
<td class="xoji-table__cell">$42.00</td>
</tr>
<tr class="xoji-table__row">
<td class="xoji-table__cell">#1025</td>
<td class="xoji-table__cell">Alan Turing</td>
<td class="xoji-table__cell">7.50</td>
</tr>
</tbody>
</table>
</xoji-table>
<script lang="ts">
import { Table } from "@xoji/svelte";
let sort: "ascending" | "descending" = $state("ascending");
</script>
<Table variant="striped" hover sticky ariaLabel="Recent orders">
<table>
<caption class="xoji-table__caption">Recent orders</caption>
<thead class="xoji-table__head">
<tr class="xoji-table__row">
<th
class="xoji-table__header-cell xoji-table__header-cell--sortable"
scope="col"
aria-sort={sort}
tabindex="0"
onclick={() => (sort = sort === "ascending" ? "descending" : "ascending")}
>
<span class="xoji-table__header-content">Order
<span class="xoji-table__sort" aria-hidden="true">
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 8l5 6H7l5-6Z" /></svg>
</span>
</span>
</th>
<th class="xoji-table__header-cell" scope="col">Customer</th>
<th class="xoji-table__header-cell" scope="col">Total</th>
</tr>
</thead>
<tbody class="xoji-table__body">
<tr class="xoji-table__row">
<td class="xoji-table__cell">#1024</td>
<td class="xoji-table__cell">Ada Lovelace</td>
<td class="xoji-table__cell">$42.00</td>
</tr>
</tbody>
</table>
</Table>
---
import { Table } from "@xoji/astro";
---
<Table variant="bordered" size="compact" aria-label="Pricing">
<table>
<thead class="xoji-table__head">
<tr class="xoji-table__row">
<th class="xoji-table__header-cell" scope="col">Plan</th>
<th class="xoji-table__header-cell" scope="col">Price</th>
</tr>
</thead>
<tbody class="xoji-table__body">
<tr class="xoji-table__row">
<th class="xoji-table__header-cell" scope="row">Starter</th>
<td class="xoji-table__cell">$0</td>
</tr>
<tr class="xoji-table__row">
<th class="xoji-table__header-cell" scope="row">Pro</th>
<td class="xoji-table__cell">2</td>
</tr>
</tbody>
</table>
</Table>