Equality
Equality

Table

A data table for displaying structured information in rows and columns.

View Markdown

Overview


Tables are built from compositional primitives that map directly to HTML table elements. Each primitive is a styled wrapper that accepts all native HTML attributes, giving you full control over layout, sizing, and responsiveness.

Rows use CSS Grid with subgrid for column sizing. Define columns once via the columns prop on <TableContainer> — all rows share the same column tracks.

  • TableContainer: Wraps the <table> in a scrollable container with elevation styling. Accepts a columns prop for CSS Grid column sizing.
  • TableHeader / TableBody / TableFooter: Semantic section wrappers (<thead>, <tbody>, <tfoot>).
  • TableRow: A table row (<tr>) that inherits column tracks via subgrid.
  • TableHead: A column header cell (<th>).
  • TableCell: A data cell (<td>).

Usage


Import the components:

import {
  TableContainer,
  TableHeader,
  TableBody,
  TableRow,
  TableHead,
  TableCell,
} from "@eqtylab/equality";

Basic usage:

<TableContainer columns="1fr 1fr auto">
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Email</TableHead>
      <TableHead>Role</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>Alice Cooper</TableCell>
      <TableCell>alice@example.com</TableCell>
      <TableCell>Admin</TableCell>
    </TableRow>
  </TableBody>
</TableContainer>

Variants


Default

Clickable Rows

Rows accept an onClick handler, which enables hover interactions. Use data-state="selected" to mark a row as selected.

Try clicking a row below to toggle its selected state!

Sortable Columns

Use <SortButton> inside <TableHead> cells to add interactive sort controls.

Usage

import {
  SortButton,
  TableContainer,
  TableHeader,
  TableRow,
  TableHead,
} from "@eqtylab/equality";

<TableContainer columns="1fr 1fr auto auto">
  <TableHeader>
    <TableRow>
      <TableHead>
        <SortButton
          field="name"
          sortField={sortField}
          sortDirection={sortDirection}
          onSort={handleSort}
        >
          Name
        </SortButton>
      </TableHead>
    </TableRow>
  </TableHeader>
</TableContainer>;

With Border

Use the border prop on TableContainer to apply an elevation-aware border with rounded corners. This should be added most places the table is used, except when it lives within a container that already has a border.

Usage

<TableContainer columns="1fr 1fr auto" border>
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Email</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>{/* rows */}</TableBody>
</TableContainer>

Empty State

When there are no rows, render empty state content in a <TableCell> that spans all columns with style={{ gridColumn: '1 / -1' }}.

Empty State with Custom Component

The empty state cell accepts any ReactNode, so you can use a custom component like EmptyTableState.

Use the sticky prop on <TableHeader> to keep column headers visible while scrolling. The height of the <TableContainer> must be constrained for this to work as expected.

Usage

<TableContainer columns="1fr 1fr auto" className="max-h-[400px]">
  <TableHeader sticky>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Email</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>{/* rows */}</TableBody>
</TableContainer>

Column Sizing


The columns prop accepts a CSS grid-template-columns value. All rows share the same column tracks via CSS subgrid.

Fixed and Flexible Columns

Mix fr units for flexible columns with fixed pixel values for predictable sizing.

Usage

<TableContainer columns="3fr 3fr 100px 100px 60px">
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Email</TableHead>
      <TableHead>Role</TableHead>
      <TableHead>Status</TableHead>
      <TableHead />
    </TableRow>
  </TableHeader>
</TableContainer>

Content-Sized Columns

Use auto for columns that should shrink to fit their content. This is useful for action columns or icon-only columns.

<TableContainer columns="1fr 1fr auto auto auto">

Truncation


Use Tailwind’s truncate min-w-0 classes directly on <TableHead> and <TableCell> to clip overflowing text with an ellipsis. For truncation to work, the column must use minmax(0, *) in the columns definition so cells can shrink below their content size.

Usage

<TableContainer columns="minmax(0,5fr) minmax(0,8fr) 100px 100px 60px">
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead className="min-w-0 truncate">Email</TableHead>
      <TableHead>Role</TableHead>
      <TableHead>Status</TableHead>
      <TableHead />
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>Alice Cooper</TableCell>
      <TableCell className="min-w-0 truncate">
        alice.cooper.very.long.email@example.com
      </TableCell>
      <TableCell>Admin</TableCell>
      <TableCell>Active</TableCell>
      <TableCell />
    </TableRow>
  </TableBody>
</TableContainer>

Responsive Columns


Use container queries to override the --table-columns CSS variable at each breakpoint, matching the number of visible columns. Apply hidden/@md:block on cells for columns that should collapse. Hidden cells are removed from the grid flow, and the remaining visible cells auto-place into the available tracks. Use className instead of the columns prop so that responsive overrides aren’t blocked by inline style specificity.

Usage

<div className="@container">
  <TableContainer className="[--table-columns:1fr_auto_auto] @md:[--table-columns:1fr_1fr_auto_auto] @lg:[--table-columns:1fr_1fr_auto_auto_auto]">
    <TableHeader>
      <TableRow>
        <TableHead>Name</TableHead>
        <TableHead className="hidden @md:block">Email</TableHead>
        <TableHead className="hidden @lg:block">Role</TableHead>
        <TableHead>Status</TableHead>
        <TableHead />
      </TableRow>
    </TableHeader>
    <TableBody>
      <TableRow>
        <TableCell>Alice Cooper</TableCell>
        <TableCell className="hidden @md:block">alice@example.com</TableCell>
        <TableCell className="hidden @lg:block">Admin</TableCell>
        <TableCell>
          <Badge variant="success">Active</Badge>
        </TableCell>
        <TableCell />
      </TableRow>
    </TableBody>
  </TableContainer>
</div>

Elevations


Sunken

Base (default)

Raised

Overlay

Props


TableContainer

NameDescriptionTypeDefaultRequired
columnsCSS grid-template-columns value for column sizingstring
elevationControls the shadow and background stylingsunken, base, raised, overlayraised
borderApplies an elevation-aware border with rounded cornersbooleanfalse

TableHeader

NameDescriptionTypeDefaultRequired
stickyKeeps the header visible while the table scrollsbooleanfalse

TableRow

NameDescriptionTypeDefaultRequired
clickableApplies hover and cursor interaction stylesbooleanfalse