---
title: Table
description: Tables display structured data in rows and columns with support for sorting, selection, column resizing, and infinite scrolling.
icon: updated

links:
  rac: Table
  source: table/table.tsx
  styles: table.css
  storybook: Components/Data Display/Table
---

## Usage

```tsx
import { Table } from '@heroui/react';
```

<ComponentPreview
  name="table-basic"
  minHeight="320px"
/>

## Anatomy

```tsx
import { Table } from '@heroui/react';

export default () => (
  <Table>
    <Table.ScrollContainer>
      <Table.Content aria-label="Example table">
        <Table.Header>
          <Table.Column>Name</Table.Column>
          <Table.Column>Role</Table.Column>
        </Table.Header>
        <Table.Body>
          <Table.Row>
            <Table.Cell>Kate Moore</Table.Cell>
            <Table.Cell>CEO</Table.Cell>
          </Table.Row>
        </Table.Body>
      </Table.Content>
    </Table.ScrollContainer>
    <Table.Footer>{/* Optional footer content */}</Table.Footer>
  </Table>
);
```

## Examples

### Secondary Variant

<ComponentPreview
  name="table-secondary-variant"
  minHeight="320px"
/>

### Async Loading

Use `Table.LoadMore` for infinite scrolling. It renders a sentinel row that triggers `onLoadMore` when scrolled into view.

<ComponentPreview
  name="table-async-loading"
  minHeight="380px"
/>

### Sorting

Columns can be made sortable using the `allowsSorting` prop on `Table.Column`. Use `sortDescriptor` and `onSortChange` on `Table.Content` to manage sort state. Wrap the label in `Table.SortableColumnHeader` and forward the `sortDirection` from the column's render prop to render the default ascending/descending indicator.

<ComponentPreview
  name="table-sorting"
  minHeight="380px"
/>

### Selection

Enable row selection with `selectionMode` on `Table.Content`. Use `Checkbox` with `slot="selection"` for select-all and per-row checkboxes.

<ComponentPreview
  name="table-selection"
  minHeight="380px"
/>

### Expandable Rows

Rows can be nested to display hierarchical data. Use the `treeColumn` prop to designate a column, and render a `Button` with `slot="chevron"` in that column’s cells so users can expand and collapse the row. Use the `expandedKeys` prop to control which rows are expanded.

<ComponentPreview
  name="table-expandable-rows"
  minHeight="420px"
/>

### Pagination

Use `Table.Footer` to add a pagination component below the table.

<ComponentPreview
  name="table-pagination"
  minHeight="400px"
/>

### Column Resizing

Wrap the table in `Table.ResizableContainer` and add `Table.ColumnResizer` inside each resizable column.

<ComponentPreview
  name="table-column-resizing"
  minHeight="420px"
/>

### Empty State

Use `renderEmptyState` on `Table.Body` to display a custom message when the table has no data.

<ComponentPreview
  name="table-empty-state"
  minHeight="300px"
/>

### Virtualization

Table supports virtualization through [Virtualizer](https://react-aria.adobe.com/Virtualizer), enabling efficient rendering of large datasets by displaying only the rows visible within the viewport.

<ComponentPreview
  name="table-virtualization"
  minHeight="380px"
/>

### TanStack Table

HeroUI's Table works as a rendering layer on top of headless table libraries.
This example uses [TanStack Table](https://tanstack.com/table) for column
definitions, sorting, and pagination — while HeroUI handles styling and
accessibility.

<ComponentPreview
  name="table-tanstack-table"
  minHeight="420px"
/>

### Custom Cells

<ComponentPreview
  name="table-custom-cells"
  minHeight="420px"
/>

## Customization

### Tailwind CSS

<ComponentPreview name="table-custom-styles" />

### Global CSS

To customize the Table component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).

```css
@layer components {
  .table-root {
    @apply relative grid w-full overflow-clip;
  }

  .table__header {
    @apply bg-gray-100;
  }

  .table__column {
    @apply px-4 py-2.5 text-left text-xs font-medium text-gray-600;
  }

  .table__row {
    @apply bg-white border-b border-gray-200;
  }

  .table__cell {
    @apply px-4 py-3 text-sm;
  }

  .table__footer {
    @apply flex items-center px-4 py-2.5;
  }
}
```

## Styling Reference

HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.

### CSS Classes

The Table component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/table.css)):

#### Base Classes [!toc]
- `.table-root` - Root container (named `table-root` instead of `table` because `table` is a built-in Tailwind CSS utility class for `display: table`)
- `.table__scroll-container` - Horizontal scroll wrapper with custom scrollbar
- `.table__content` - The `<table>` element
- `.table__header` - Header row (`<thead>`)
- `.table__column` - Column header cell (`<th>`)
- `.table__body` - Body section (`<tbody>`)
- `.table__row` - Row element (`<tr>`)
- `.table__cell` - Data cell (`<td>`)
- `.table__footer` - Footer container (outside table)

#### Advanced Classes [!toc]
- `.table__column-resizer` - Drag handle for column resizing
- `.table__resizable-container` - Wrapper enabling column resizing
- `.table__load-more` - Sentinel row for infinite scrolling
- `.table__load-more-content` - Styled container for the loading indicator

#### Variant Classes [!toc]
- `.table-root--primary` - Gray background container with card-style body (default)
- `.table-root--secondary` - No background, standalone rounded headers

### Interactive States

The Table supports both CSS pseudo-classes and data attributes for flexibility:

- **Hover**: `:hover` or `[data-hovered="true"]` (row background change)
- **Selected**: `[data-selected="true"]` (row highlight)
- **Focus**: `:focus-visible` or `[data-focus-visible="true"]` (inset focus ring on rows, columns, and cells)
- **Disabled**: `:disabled` or `[aria-disabled="true"]` (reduced opacity)
- **Sortable**: `[data-allows-sorting="true"]` (interactive cursor on columns)
- **Dragging**: `[data-dragging="true"]` (reduced opacity)
- **Drop Target**: `[data-drop-target="true"]` (accent background)

## API Reference

### Table

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant. Primary has a gray background container; secondary is flat with transparent rows. |
| `className` | `string` | - | Additional CSS classes for the root container |
| `children` | `React.ReactNode` | - | Table content (ScrollContainer, Footer, etc.) |

### Table.ScrollContainer

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Table.Content element |

### Table.Content

Inherits from [React Aria Table](https://react-spectrum.adobe.com/react-aria/Table.html).

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `aria-label` | `string` | - | Accessible label for the table |
| `selectionMode` | `"none" \| "single" \| "multiple"` | `"none"` | Selection behavior |
| `selectedKeys` | `Selection` | - | Controlled selected keys |
| `onSelectionChange` | `(keys: Selection) => void` | - | Selection change handler |
| `sortDescriptor` | `SortDescriptor` | - | Current sort state |
| `onSortChange` | `(descriptor: SortDescriptor) => void` | - | Sort change handler |
| `className` | `string` | - | Additional CSS classes |

### Table.Header

Inherits from [React Aria TableHeader](https://react-spectrum.adobe.com/react-aria/Table.html#tableheader).

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `columns` | `T[]` | - | Dynamic column data for render prop pattern |
| `children` | `React.ReactNode \| (column: T) => React.ReactNode` | - | Static columns or render prop |

### Table.Column

Inherits from [React Aria Column](https://react-spectrum.adobe.com/react-aria/Table.html#column).

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `id` | `string` | - | Column identifier |
| `allowsSorting` | `boolean` | `false` | Whether the column is sortable |
| `isRowHeader` | `boolean` | `false` | Whether this column is a row header |
| `defaultWidth` | `string \| number` | - | Default width for resizable columns |
| `minWidth` | `number` | - | Minimum width for resizable columns |
| `children` | `React.ReactNode \| (values: ColumnRenderProps) => React.ReactNode` | - | Column content or render prop with sort direction |

### Table.Body

Inherits from [React Aria TableBody](https://react-spectrum.adobe.com/react-aria/Table.html#tablebody).

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `items` | `T[]` | - | Dynamic row data for render prop pattern |
| `renderEmptyState` | `() => React.ReactNode` | - | Content to display when the table is empty |
| `children` | `React.ReactNode \| (item: T) => React.ReactNode` | - | Static rows or render prop |

### Table.Row

Inherits from [React Aria Row](https://react-spectrum.adobe.com/react-aria/Table.html#row).

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `id` | `string \| number` | - | Row identifier |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Row cells |

### Table.Cell

Inherits from [React Aria Cell](https://react-spectrum.adobe.com/react-aria/Table.html#cell).

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Cell content |

### Table.SortableColumnHeader

Renders a sortable column label with an ascending/descending indicator. Use it inside a `Table.Column` render-prop callback and forward the `sortDirection` value.

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `sortDirection` | `"ascending" \| "descending"` | - | Current sort direction. Pass through from the `Table.Column` render prop. |
| `showIndicator` | `boolean` | `true` | Whether to render the sort indicator icon when a direction is set. |
| `indicator` | `React.ReactNode` | - | Custom indicator element. Overrides the default chevron and receives a `data-direction` attribute. |
| `className` | `string` | - | Additional CSS classes for the wrapper. |
| `children` | `React.ReactNode` | - | Column label content |

### Table.Footer

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Footer content (e.g., pagination) |

### Table.ColumnResizer

Inherits from [React Aria ColumnResizer](https://react-spectrum.adobe.com/react-aria/Table.html#columnresizer).

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |

### Table.ResizableContainer

Inherits from [React Aria ResizableTableContainer](https://react-spectrum.adobe.com/react-aria/Table.html#resizabletablecontainer).

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Table.Content element |

### Table.LoadMore

Inherits from [React Aria TableLoadMoreItem](https://react-spectrum.adobe.com/react-aria/Table.html).

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `isLoading` | `boolean` | `false` | Whether data is currently loading |
| `onLoadMore` | `() => void` | - | Handler called when the sentinel row is visible |
| `children` | `React.ReactNode` | - | Loading indicator content |

### Table.LoadMoreContent

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Loading indicator content (e.g., Spinner) |

### Table.Collection

Re-exported from React Aria `Collection`. Used to render dynamic cells within rows alongside static cells (e.g., checkboxes).

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `items` | `T[]` | - | Collection items |
| `children` | `(item: T) => React.ReactNode` | - | Render prop for each item |

### TableLayout

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `rowHeight` | `number \| undefined` | 48 | The fixed height of a row in px. |
| `estimatedRowHeight` | `number \| undefined` | — | The estimated height of a row, when row heights are variable. |
| `headingHeight` | `number \| undefined` | 48 | The fixed height of a section header in px. |
| `estimatedHeadingHeight` | `number \| undefined` | — | The estimated height of a section header, when the height is variable. |
| `loaderHeight` | `number \| undefined` | 48 | The fixed height of a loader element in px. This loader is specifically for "load more" elements rendered when loading more rows at the root level or inside nested row/sections. |
| `dropIndicatorThickness` | `number \| undefined` | 2 | The thickness of the drop indicator. |
| `gap` | `number \| undefined` | 0 | The gap between items. |
| `padding` | `number \| undefined` | 0 | The padding around the list. |

## Related Components

<RelatedComponents component="table" />
