Skip to content

Documentation

neelam-ui

Search documentation

Getting Started
Forms
Overlays
Navigation
Data Display
Layout
AI & Chat
Blocks
GitHub repository

Table

A native <table>, styled — so row and column associations, and a screen reader's table-navigation commands, all come from the browser.

Recent invoices.
InvoiceStatusMethodAmount
INV-001PaidCredit card$250.00
INV-002PendingPayPal$150.00
INV-003UnpaidBank transfer$350.00
Total$750.00

Usage#

import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "neelam-ui";
 
<Table>
  <TableCaption>Recent invoices.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead>Invoice</TableHead>
      <TableHead className="text-right">Amount</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableHead scope="row">INV-001</TableHead>
      <TableCell className="text-right">$250.00</TableCell>
    </TableRow>
  </TableBody>
</Table>

The parts#

ComponentElementNotes
Table<table>Wrapped in a horizontally scrolling container
TableHeader<thead>
TableBody<tbody>
TableFooter<tfoot>Totals row
TableRow<tr>
TableHead<th>scope="col" by default
TableCell<td>
TableCaption<caption>Rendered below the table

Scrolling, not reflowing#

Table wraps itself in an overflow-x-auto container. A table's columns do not reflow the way text wraps, so on a narrow viewport the wrapper scrolls rather than the table overflowing the page.

A scrolling region needs to be keyboard-scrollable

WCAG 2.1.1 requires that anything scrollable by pointer also be scrollable by keyboard, which means the scroll container has to be focusable. If you build your own wrapper instead of using Table's, give it tabIndex={0} and an aria-label — that is exactly what the props tables on this site do.

Row headers#

TableHead defaults to scope="col", which is right in TableHeader. For the cell that identifies a row — an invoice number, a person's name — use TableHead scope="row" rather than a TableCell:

<TableHead scope="row">INV-001</TableHead>

This is what lets a screen reader announce "INV-001, Amount, $250.00" as it moves across the row, instead of reading bare values with no anchor.

Table or DataTable?#

Table is presentational — you render the rows. Reach for DataTable when you want sorting, filtering, and pagination over a set of rows without wiring them yourself. DataTable is built on these same parts.

Keyboard#

Keyboard shortcuts
KeyBehaviour
TabReaches the scroll container, then any interactive content in cells. Cells themselves are not focusable.
Scrolls the container horizontally when it has focus.

A table is content, not a composite widget, so there is no grid-style arrow-key navigation between cells. Screen readers provide their own table-navigation commands, which work because the markup is a real <table>.

Accessibility#

  • Real <table> markup throughout, so header/cell associations, row and column counts, and table navigation come from the platform rather than from ARIA.
  • TableCaption renders a real <caption>, which names the table for assistive tech. It is visually placed below via caption-bottom but remains the first thing announced.
  • scope is set on every TableHead"col" by default, "row" when you say so — which is what makes cell announcements meaningful.
  • Alignment is done with utility classes on cells rather than the deprecated align attribute.

Never use a table for layout

If the content is not tabular, a table's semantics actively mislead — screen readers announce dimensions and offer navigation that makes no sense. Use CSS grid or flex instead.

API reference#

The table parts add no props of their own beyond TableHead's scope default — each is a styled wrapper around its native element, and every attribute passes straight through. See the table above for which element each renders.