Table

Table preserves native HTML table semantics and adds an optional responsive overflow container without owning data-grid behavior.

Overview and decision boundary

Use Table to present structured records for scanning, comparison, and repeated operations.

Do not use Table for layout grids or card collections.

Ethan LeeVP SalesActiveethan@acme.com
John SmithCTOActivejohn@acme.com
Kate MooreCEOActivekate@acme.com
Lena OrtizCOOActivelena@acme.com
1 to 4 of 8 results
import { ArrowUp, ChevronDown } from "@nerio-ui/adapters/icons";import { Pagination, Table, TableBody, TableCell,  TableContainer, TableHead, TableHeader, TableRow } from "@nerio-ui/ui";import { Button, Checkbox } from "@nerio-ui/ui/client";<div className="table-shell">  <TableContainer aria-label="Team members">    <Table>      <TableHeader>        <TableRow>          <TableHead aria-label="Select rows">            <Checkbox aria-label="Select all rows on this page" />          </TableHead>          <TableHead aria-sort="ascending">            <Button trailingIcon={ArrowUp} variant="ghost">Name</Button>          </TableHead>          <TableHead aria-sort="none">            <Button trailingIcon={ChevronDown} variant="ghost">Role</Button>          </TableHead>          <TableHead aria-sort="none">            <Button trailingIcon={ChevronDown} variant="ghost">Status</Button>          </TableHead>          <TableHead aria-sort="none">            <Button trailingIcon={ChevronDown} variant="ghost">Email</Button>          </TableHead>        </TableRow>      </TableHeader>      <TableBody>...</TableBody>    </Table>  </TableContainer>  <div className="table-footer">    <span>1 to 4 of 8 results</span>    <Pagination pages={pages} />  </div></div>

Installation and imports

Install the editable registry source, or use the matching package entrypoint when the product keeps Nerio as a workspace dependency.

pnpm dlx nerio add tableimport { Table, TableBody, TableCell, TableContainer, TableHead, TableHeader, TableRow } from '@nerio-ui/ui';

Usage

import { Table, TableBody, TableCell, TableContainer, TableHead, TableHeader, TableRow } from '@nerio-ui/ui';<h2 id="projects-title">Projects</h2><TableContainer focusable aria-labelledby="projects-title">  <Table>    <TableHeader><TableRow><TableHead>Name</TableHead></TableRow></TableHeader>    <TableBody><TableRow><TableCell>Roadmap</TableCell></TableRow></TableBody>  </Table></TableContainer>

Variants

Ethan LeeVP SalesActiveethan@acme.com
John SmithCTOActivejohn@acme.com
Kate MooreCEOActivekate@acme.com
Lena OrtizCOOActivelena@acme.com
1 to 4 of 8 results
import { ArrowUp, ChevronDown } from "@nerio-ui/adapters/icons";import { Pagination, Table, TableBody, TableCell,  TableContainer, TableHead, TableHeader, TableRow } from "@nerio-ui/ui";import { Button, Checkbox } from "@nerio-ui/ui/client";<div className="table-shell-secondary">  <TableContainer aria-label="Team members">    <Table>      <TableHeader>        <TableRow>          <TableHead aria-label="Select rows">            <Checkbox aria-label="Select all rows on this page" />          </TableHead>          <TableHead aria-sort="ascending">            <Button trailingIcon={ArrowUp} variant="ghost">Name</Button>          </TableHead>          <TableHead aria-sort="none">            <Button trailingIcon={ChevronDown} variant="ghost">Role</Button>          </TableHead>          <TableHead aria-sort="none">            <Button trailingIcon={ChevronDown} variant="ghost">Status</Button>          </TableHead>          <TableHead aria-sort="none">            <Button trailingIcon={ChevronDown} variant="ghost">Email</Button>          </TableHead>        </TableRow>      </TableHeader>      <TableBody>...</TableBody>    </Table>  </TableContainer>  <div className="table-footer">    <span>1 to 4 of 8 results</span>    <Pagination pages={pages} />  </div></div>
Recipe / modeContract
Primary compositionMuted consumer-owned frame, inset row group, and Pagination footer on the same surface; this is a composition recipe, not a Table prop.
Secondary compositionMuted rounded header with open page-level rows and footer; this is a composition recipe, not a Table prop.
PlainResponsive overflow wrapper with no region or tab stop.
Namedaria-label or aria-labelledby exposes an optional non-focusable region.
Focusablefocusable={true} opts into one keyboard-scroll region only when aria-label or aria-labelledby is a non-empty runtime string.

Anatomy

SlotNative element / purpose
containerOptional div overflow region; directly wrap one Table and never nest containers.
root / captiontable and caption preserve the table's accessible name and description.
header / body / footerthead, tbody, and tfoot retain native row-group semantics.
row / head / celltr, th, and td forward scope, headers, colSpan, rowSpan, aria-sort, and data attributes.

States

ProjectOwnerStatusUpdated

No projects found

Try adjusting the current filters.

<TableRow><TableCell colSpan={4}><EmptyState size="sm"><EmptyStateMedia>...</EmptyStateMedia><EmptyStateHeader>...</EmptyStateHeader><EmptyStateActions><Button variant="secondary">Create project</Button></EmptyStateActions></EmptyState></TableCell></TableRow>
ProjectOwnerStatusUpdated
<TableHeader><TableRow>...</TableRow></TableHeader><TableBody><TableRow aria-hidden="true"><TableCell><Skeleton /></TableCell>...</TableRow></TableBody>
PatternContract
EmptyRender EmptyState inside one TableCell with colSpan equal to the visible column count.
LoadingCompose Skeleton cells; put aria-busy on the named container and hide purely visual rows.
Selected / currentUse data-selected for consumer state or aria-current only on tbody rows that are genuinely current; aria-current="false" stays neutral.
Focus withinInteractive tbody cell controls highlight their row but keep their own keyboard target and label; header and footer rows stay stable.
Disabled-lookingdata-disabled is visual only; disable each nested control independently when required.
Destructivedata-tone="danger" emphasizes a value without changing its semantics.

Motion

  • State changes should use shared motion tokens and preserve reduced-motion behavior.

Accessibility

  • Preserve native captions, row groups, scope, headers, colSpan, rowSpan, and aria-sort relationships.
  • Keep sorting, filtering, pagination, selection, and advanced table behavior out of this Core primitive.
  • TableContainer is plain by default; focusable={true} creates a tab stop only when aria-label or aria-labelledby is a non-empty runtime string.
  • TableContainer owns its region role, tab stop, focusable marker, and normalized accessible-name attributes after consumer props are spread.
  • Hover, focus-within, selected, and current styling applies only to tbody rows; aria-current="false" remains neutral and header/footer rows remain stable.
  • TableCell text wraps by default; opt into nowrap only for bounded identifiers or short values.
  • Directly wrap one Table in TableContainer and never nest scroll containers.
  • Keep row links and actions independently labelled instead of making the whole row clickable.

API

PartPropsPurpose
TableTableHTMLAttributesNative table props, caption relationships, direction, and data attributes.
TableContainerfocusable, aria-label, aria-labelledbyOptional horizontal overflow and runtime-safe named keyboard region; owned region props cannot be overridden.
TableHeadThHTMLAttributesscope defaults to col and remains overrideable for row or grouped headers.
TableCellTdHTMLAttributesWraps text by default and forwards headers, colSpan, rowSpan, numeric alignment, values, and actions.

Implementation contract

Table is server-safe and owns presentation only. Sorting, filtering, selection, pagination state, resizing, reordering, virtualization, bulk actions, and fetching stay consumer-owned or belong to Nerio Pro. Consumer sticky headers and columns can use position: sticky on cells; the component adds no transform, isolation, or table clipping. If vertical stickiness is needed, give the single TableContainer an explicit block-size and vertical overflow instead of nesting another scroller.

Styling contract

ContractValue
AuthoringComplete, statically detectable Tailwind CSS v4 recipes own component visuals.
ValuesSemantic and component --n-* variables remain the canonical customization layer.
OverridesCustomizable slots merge consumer className values with tailwindCn so conflicting utilities resolve deterministically.
Residual CSSOnly shared keyframes and scoped no-Preflight compatibility rules remain; there is no parallel visual selector layer.

Design notes

  • Use Table to present structured records for scanning, comparison, and repeated operations.

Do / do not

Do

Keep the default text wrapping for readable records, use native headers and captions, keep row links or actions as separately labelled controls, and expose sortable state with aria-sort.

Do not

Make the entire row clickable, nest scroll containers, or move DataGrid behavior into this primitive.

Tokens

GroupTokensControls
Container--n-table-container-background, --n-table-container-border, --n-table-container-radius, --n-table-container-focus-ringMuted grouping surface, boundary, and focus treatment.
Rows--n-table-row-min-height, --n-table-row-group-radius, --n-table-row-background-hover, --n-table-row-background-selected, --n-table-row-selection-indicator, --n-table-row-selection-indicator-widthCompensated inner radius, density, smooth interaction, and neutral selected/current state cues.
Cells--n-table-cell-padding-x, --n-table-cell-padding-yComfortable and compact spacing.
Headers--n-table-header-background, --n-table-header-foregroundQuiet semantic hierarchy.
Values--n-table-cell-foreground-disabled, --n-table-cell-foreground-dangerMuted and destructive values.