Table
A data table for displaying structured information in rows and columns.
View MarkdownOverview
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 acolumnsprop 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.
Sticky Header
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
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
columns | CSS grid-template-columns value for column sizing | string | — | ❌ |
elevation | Controls the shadow and background styling | sunken, base, raised, overlay | raised | ❌ |
border | Applies an elevation-aware border with rounded corners | boolean | false | ❌ |
TableHeader
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
sticky | Keeps the header visible while the table scrolls | boolean | false | ❌ |
TableRow
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
clickable | Applies hover and cursor interaction styles | boolean | false | ❌ |