Table
A native <table>, styled — so row and column associations, and a screen reader's table-navigation commands, all come from the browser.
| Invoice | Status | Method | Amount |
|---|---|---|---|
| INV-001 | Paid | Credit card | $250.00 |
| INV-002 | Pending | PayPal | $150.00 |
| INV-003 | Unpaid | Bank 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#
| Component | Element | Notes |
|---|---|---|
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#
| Key | Behaviour |
|---|---|
| Tab | Reaches 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. TableCaptionrenders a real<caption>, which names the table for assistive tech. It is visually placed below viacaption-bottombut remains the first thing announced.scopeis set on everyTableHead—"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
alignattribute.
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.