---
title: "Table"
description: "A data table for displaying structured information in rows and columns."
source: "Equality"
---
import { TableDemo } from "@demo/components/demo/table";
import { ELEVATION } from "@eqtylab/equality";

## Overview

---

Tables are built from compositional primitives that map directly to [HTML table elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/table). 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](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/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:

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

Basic usage:

```tsx
<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

<TableDemo client:only="react" />

### Clickable Rows

Rows accept an `onClick` handler, which enables hover interactions.

<TableDemo client:only="react" variant="clickable" />

### Sortable Columns

Use [`<SortButton>`](/components/sort-button) inside `<TableHead>` cells to add interactive sort controls.

<TableDemo client:only="react" variant="with-sorter" />

#### Usage

```tsx
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.

<TableDemo client:only="react" variant="with-border" />

#### Usage

```tsx
<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' }}`.

<TableDemo client:only="react" variant="empty-state" />

### Empty State with Custom Component

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

<TableDemo client:only="react" variant="empty-state-custom" />

### 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.

<TableDemo client:only="react" variant="sticky-header" />

#### Usage

```tsx
<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`](https://developer.mozilla.org/en-US/docs/Web/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.

<TableDemo client:only="react" variant="column-sizing" />

#### Usage

```tsx
<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.

```tsx
<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.

<TableDemo client:only="react" variant="truncation" />

#### Usage

```tsx
<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](https://tailwindcss.com/docs/responsive-design#what-are-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.

<TableDemo client:only="react" variant="responsive" />

#### Usage

```tsx
<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

<TableDemo client:only="react" elevation={ELEVATION.SUNKEN} />

### Base (default)

<TableDemo client:only="react" elevation={ELEVATION.BASE} />

### Raised

<TableDemo client:only="react" elevation={ELEVATION.RAISED} />

### Overlay

<TableDemo client:only="react" elevation={ELEVATION.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` | ❌       |