Table

Renders tabular data as a real HTML table — headers, rows, footer — at one 32px row track.

import { Table } from "@usefragments/ui";Source

Preview

<Table aria-label="Team members">
  <Table.Head>
    <Table.Row>
      <Table.HeaderCell>Name</Table.HeaderCell>
      <Table.HeaderCell>Role</Table.HeaderCell>
      <Table.HeaderCell>Status</Table.HeaderCell>

Usage

Use when

  • The data is static — read it, do not interact with it
  • You need real thead/tbody/tfoot semantics
  • The table needs a caption or a totals footer

Avoid when

  • Rows need sorting, selection, or clicking (use DataTable)
  • Content is a single column of items (use List)

Implementation notes

  • Header cells go in Table.HeaderCell — it sets scope="col" for you
  • Give every table a caption or aria-label; visuallyHidden keeps it off screen
  • Every row is the 32px control track; there is no density or stripe option
  • Set maxHeight when the head must stay in view: the rows scroll inside the table and the head sticks; without it the page scrolls the rows and the head with them
  • The first column starts 12 in and every other cell pads 8, so content never meets a selected row's ring
  • Show an empty result with Table.Empty and a load with Table.Loading so the header stays real
  • Narrow viewports scroll the table sideways — keep column count low
  • Numeric columns: align="end" on the Table.HeaderCell and each Table.Cell, with tabularNums on the cells

Installation

pnpm add @usefragments/ui

Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Anatomy

Import Table once, then compose its parts with dot notation.

  • Table.Head
  • Table.Body
  • Table.Footer
  • Table.Row
  • Table.Cell
  • Table.HeaderCell
  • Table.Caption
  • Table.Empty
  • Table.Loading

Header row plus body rows, no extra styling.

<Table aria-label="Team members">
  <Table.Head>
    <Table.Row>
      <Table.HeaderCell>Name</Table.HeaderCell>
      <Table.HeaderCell>Role</Table.HeaderCell>
      <Table.HeaderCell>Status</Table.HeaderCell>
    </Table.Row>
  </Table.Head>
  <Table.Body>
    <Table.Row>
      <Table.Cell>Alice Johnson</Table.Cell>
      <Table.Cell>Engineer</Table.Cell>
      <Table.Cell>Active</Table.Cell>
    </Table.Row>
    <Table.Row>
      <Table.Cell>Bob Smith</Table.Cell>
      <Table.Cell>Designer</Table.Cell>
      <Table.Cell>Active</Table.Cell>

Examples

Empty

Keep the headers; Table.Empty spans the columns with a compact EmptyState.

<Table aria-label="Team members">
  <Table.Head>
    <Table.Row>
      <Table.HeaderCell>Name</Table.HeaderCell>
      <Table.HeaderCell>Role</Table.HeaderCell>
      <Table.HeaderCell>Status</Table.HeaderCell>

Loading

Placeholder rows at the row track, one bar per cell, under the real header.

<Table aria-label="Team members">
  <Table.Head>
    <Table.Row>
      <Table.HeaderCell>Name</Table.HeaderCell>
      <Table.HeaderCell>Role</Table.HeaderCell>
      <Table.HeaderCell>Status</Table.HeaderCell>

Selected Row

A chosen row carries the selection wash and its 1px inset ring.

<Table aria-label="Repositories">
  <Table.Head>
    <Table.Row>
      <Table.HeaderCell>Repository</Table.HeaderCell>
      <Table.HeaderCell>Findings</Table.HeaderCell>
    </Table.Row>

Bordered

The sheet: the surface plane, a hairline border and the surface radius.

<Table bordered aria-label="Pricing">
  <Table.Head>
    <Table.Row>
      <Table.HeaderCell>Plan</Table.HeaderCell>
      <Table.HeaderCell>Price</Table.HeaderCell>
      <Table.HeaderCell>Features</Table.HeaderCell>

With Caption

A visible caption titles the data for every reader.

<Table aria-label="Q1 results">
  <Table.Caption>Quarterly Results (Q1 2026)</Table.Caption>
  <Table.Head>
    <Table.Row>
      <Table.HeaderCell>Metric</Table.HeaderCell>
      <Table.HeaderCell>Value</Table.HeaderCell>

Screen Reader Caption

The caption reaches screen readers only; the layout stays tight.

<Table aria-label="API keys">
  <Table.Caption visuallyHidden>API keys and last-used timestamps</Table.Caption>
  <Table.Head>
    <Table.Row>
      <Table.HeaderCell>Name</Table.HeaderCell>
      <Table.HeaderCell>Prefix</Table.HeaderCell>

Totals sit in a footer that stays separate from the data.

<Table bordered aria-label="Expenses">
  <Table.Head>
    <Table.Row>
      <Table.HeaderCell>Category</Table.HeaderCell>
      <Table.HeaderCell>Amount</Table.HeaderCell>
    </Table.Row>

API reference

Component props
Prop
Type
Default
Description
borderedboolean | undefinedfalseDraw the sheet: the surface plane, a hairline border and the surface radius
Caption.childrenReact.ReactNodeNot set—
Caption.visuallyHiddenboolean | undefinedNot setVisually hide the caption (screen readers only)
Cell.alignTableCellAlign | undefinedcenterstartendNot setAlign the cell's content; give the column's Table.HeaderCell the same.
Cell.childrenReact.ReactNodeNot set—
Cell.tabularNumsboolean | undefinedNot setUse tabular (fixed-width) numerals so digits align in columns. Ideal for numeric columns that update — counts, timestamps, currency.
childrenReact.ReactNodeNot set—
Empty.childrenRequiredReact.ReactNodeNot setEmptyState parts: `EmptyState.Title`, `EmptyState.Description`, `EmptyState.Actions`. They render in a compact EmptyState.
Empty.colSpanRequirednumberNot setHow many columns the row spans: the header's column count.
HeaderCell.alignTableCellAlign | undefinedcenterstartendNot setAlign the header with its column: `end` over numbers.
HeaderCell.childrenReact.ReactNodeNot set—
HeaderCell.scopestring | undefinedNot setScope for the header cell
Loading.columnsRequirednumberNot setHow many cells each placeholder row draws: the header's column count.
Loading.rowsnumber | undefinedNot setHow many placeholder rows to draw.
maxHeightstring | number | undefinedNot setBound the table's height: the wrapper scrolls on both axes and the head sticks to its top, filling its plane once rows pass under it
Row.childrenReact.ReactNodeNot set—
Row.selectedboolean | undefinedNot setMark the row as chosen: the selection wash plus its 1px inset ring.
wrapperClassNamestring | undefinedNot setClass name for the outer scroll/wrapper container
wrapperPropsReact.HTMLAttributes<HTMLDivElement> | undefinedNot setHTML attributes for the outer scroll/wrapper container

Accessibility

  • Semantic HTML table elements (thead, tbody, tfoot, th, td)
  • Column headers have scope="col" by default
  • Caption provides table description for screen readers
  • Supports visually-hidden caption for screen-reader-only labels
  • DataTablealternativeUse DataTable for sorting, selection, and TanStack features

Next steps