Skip to main content

Table

Info:Display html svelte astro Success: coverage 27/27

A styled data table around native `<table>`: zebra, bordered, hover, sticky header, and a sortable-header affordance.

Live demo

live · @xoji/astro

Table

Sortable, hover, sticky header

Click a sortable column header (or focus it and press Enter) to re-order the rows.

Algorithm Tokens Coverage Status
xoji-default 142 Success:100% Success:Blessed
xoji-quiet 138 Warning:98% Success:Blessed
xoji-loud 151 Warning:96% Warning:Draft
xoji-hc 147 Success:100% Success:Blessed
aurora 129 Warning:91% Community

Striped, compact

Component Category Consumed tokens
buttoncontrol9
fieldform11
alertfeedback7
dialogoverlay8
tabledata-display12

Bordered

Tier Audience Surface
Buildersalgorithm authorsinternal derivation
Knobseveryonedeclared inputs
Overridespower usersany token, anywhere

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.

Pair header cells with the sortable affordance and wire sorting yourself: toggle aria-sort between ascending/descending/none; the glyph follows.
Drop a Badge or Avatar into any cell for status pills and row identities.
Wrap the table in a Card for an elevated, contained data surface.

Props

5 props, straight from the manifest.

PropTypeDefaultBindingsDescription
variant TableVariant
default striped bordered
default
html svelte astro
Row and cell chrome. `striped` zebra-stripes body rows; `bordered` outlines every cell.
size TableSize
normal compact
normal
html svelte astro
Cell density. `compact` tightens the cell padding.
hover boolean false
html svelte astro
Highlights the body row under the pointer with the hover state tint.
sticky boolean false
html svelte astro
Pins the header cells to the top of the scroll container while the body scrolls.
ariaLabel string
html svelte astro
Accessible name for the table. Required when there is no `<caption>`; the binding warns when both are missing.

Appearance

Variants

default

.xoji-table

Plain rows separated by a bottom rule; no class needed.

striped

.xoji-table--striped

Alternating body-row backgrounds for scannability.

bordered

.xoji-table--bordered

A full grid; every cell gets a border.

Sizes

normal

default
.xoji-table

Default density.

compact

.xoji-table--compact

Tighter cell padding.

States

hover

.xoji-table--hover .xoji-table__body .xoji-table__row:hover

Pointer over a body row (with hover); the row paints the hover tint.

sortable

.xoji-table__header-cell--sortable

A sortable header cell: pointer cursor and a hover tint behind the label.

sorted

.xoji-table__header-cell[aria-sort="ascending"]

A header cell with aria-sort set; the glyph colors and points up (asc) or down (desc).

focus-visible

.xoji-table__header-cell--sortable: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

.xoji-table-wrap

The horizontal-scroll container that clips the table to its width and rounds the corners.

--radius-lg

table

.xoji-table

The <table> itself: collapsed borders, surface background, base typography.

--font-sans --text-body --leading-normal --fg-0 --bg-1

caption

.xoji-table__caption

An optional <caption> rendered above the table as a muted label.

--space-2 --space-4 --text-sm --fg-2

head

.xoji-table__head

The <thead> group; its last header row carries the heavier under-rule.

--border-normal --line-2

body

.xoji-table__body

The <tbody> group that zebra striping and hover target.

row

.xoji-table__row

A <tr>: the unit zebra striping and row hover apply to.

header-cell

.xoji-table__header-cell

A <th>: semibold ink on a tint, with scope and an optional sortable affordance.

--space-3 --space-4 --weight-semibold --fg-1 --bg-2 --line

cell

.xoji-table__cell

A <td>: the standard data cell with a row-separating bottom rule.

--space-3 --space-4 --line

sort

.xoji-table__sort

The decorative sort glyph inside a sortable header; rotates and colors per aria-sort.

--fg-3 --accent-text --duration-fast --ease-standard

footer-cell

.xoji-table__footer-cell

A <td>/<th> in <tfoot>: a summary cell on a tint above a heavy top rule.

--space-3 --space-4 --weight-medium --fg-1 --bg-2 --border-normal --line-2

Tokens & coverage

What the component consumes, checked live against what the algorithm produces.

Success:fully covered 27/27 consumed tokens produced default register: 276 tokens

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

default
html svelte astro

The consumer-authored <table> (with xoji-table__* part classes on its rows and cells).

Accessibility

Built on a native <table> with <thead>/<tbody>/<tfoot>, <th scope>, and an optional <caption>. Row/column semantics come for free.
Every header cell SHOULD carry scope (col or row) so assistive tech associates data cells correctly.
A table with no <caption> needs an aria-label; the binding warns at runtime when both are missing.
Sortable headers expose state via aria-sort (ascending/descending/none), not via the glyph, which is aria-hidden.
The sortable header is keyboard-focusable; focus shows a token ring plus a transparent outline the forced-colors base rule promotes to a real system outline.

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>
xoji-defaultalgorithm
darkscheme
50components+2
8categories
3libraries
276tokens+78
MITlicense