Table

Styled primitives for tabular data: a real <table> in a hairline-ringed frame that scrolls horizontally when it runs out of room. Sorting, pagination and selection logic stay in your code.

StableSource
InvoiceCustomerStatusAmount
INV-1042NorthwindPaid1,240.00
INV-1043GlobexDue860.50
INV-1044InitechOverdue3,120.00

Import

tsx
import { Table, TableHead, TableBody, TableRow, TableHeader, TableCell } from "@merid/react";

Examples

Dense, without hover

density="sm" tightens cells to 10px × 12px; hoverable={false} turns off the row hover tint for read-only tables.

KeyRequests
api/search12,480
api/users3,904

Row selection

selected on a row applies the accent-soft fill. Pair it with a Checkbox column so selection is operable and announced. scrollLabel makes the frame a focusable, named region so keyboard users can scroll it.

InvoiceAmount
INV-10421,240.00
INV-1043860.50
INV-10443,120.00

Sortable columns

Give a header sortDirection and onSort: it sets aria-sort on the <th> and wraps the label in a real button with a direction icon. Sorting the rows stays yours; use "none" on sortable columns that are not the active sort.

tsx
const [sort, setSort] = useState<{ key: "name" | "amount"; dir: "ascending" | "descending" }>({ key: "name", dir: "ascending" });
const toggle = (key: "name" | "amount") =>
  setSort((s) => ({ key, dir: s.key === key && s.dir === "ascending" ? "descending" : "ascending" }));

<TableHeader sortDirection={sort.key === "name" ? sort.dir : "none"} onSort={() => toggle("name")}>
  Name
</TableHeader>

API reference

Every part forwards its ref to the matching native element and passes native attributes through.

Table

The frame div wraps the <table>; className and native props go on the <table>.

PropTypeDefaultDescription
density"sm" | "md""md"Row density: md 14px 16px cells, sm 10px 12px.
hoverablebooleantrueHighlights rows on hover.
scrollLabelstring–Makes the scroll wrapper a focusable region with this name. Omit for tables that never overflow.

TableHead, TableBody

<thead> (on the subtle surface) and <tbody>. No props beyond native attributes.

TableRow

PropTypeDefaultDescription
selectedboolean–Accent-soft fill via `data-selected`. Sets no ARIA: convey selection through the selection control (e.g. a checked checkbox cell), and pass `aria-selected` yourself only on a `role="grid"` table.

TableHeader

PropTypeDefaultDescription
align"start" | "center" | "end""start"Text alignment; use end for numeric columns.
scope"col" | "row" | "colgroup" | "rowgroup""col"Native header scope.
sortDirection"ascending" | "descending" | "none"–Makes the column sortable: sets aria-sort and renders the label inside a button with a direction icon.
onSort(event) => void–Called when the sort button is pressed. Compute the next direction and sort the rows yourself.

TableCell

PropTypeDefaultDescription
align"start" | "center" | "end""start"Text alignment; use end for numeric columns.

Styling

HookValues
.mrd-table-frameScrollable, hairline-ringed wrapper
.mrd-tableThe <table>; data-density = sm · md, data-hoverable
.mrd-table__head, .mrd-table__bodySections
.mrd-table__rowRows; data-selected when selected
.mrd-table__header, .mrd-table__cellCells; data-align = start · center · end

Component variable: --mrd-table-cell-padding, set per density.

Accessibility

  • It is a native table, so screen readers get row and column navigation for free. TableHeader defaults to scope="col"; use scope="row" for row headers.
  • Add a <caption> (it can be visually hidden with VisuallyHidden) or aria-label on the table to describe it.
  • TableRow selected is visual only (data-selected); a plain table has no selection semantics, so the selection control in the row must carry the state.
  • When the table can overflow, set scrollLabel so the frame is reachable with Tab and scrollable with the arrow keys.
KeyAction
TabMoves focus to the scroll region (when scrollLabel is set) and to interactive content in cells.
←→Scrolls the focused region horizontally.

Guidelines

Do

Right-align numbers with align="end" so digits line up; cells already use tabular numerals.

Avoid

Use a table for layout, or centre-align text columns.