# Table **Category**: react **URL**: https://www.heroui.com/docs/react/migration/table **Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/react/migration/(components)/table.mdx > Migration guide for Table from HeroUI v2 to v3 *** Refer to the [v3 Table documentation](/docs/react/components/table) for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2. ## Structure Changes In v2, Table used separate named imports for each part: ```tsx import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from "@heroui/react"; export default function App() { return ( Name Role Kate Moore CEO
); } ``` In v3, Table uses the compound component pattern with dot notation and adds `Table.ScrollContainer` and `Table.Content`: ```tsx import { Table } from "@heroui/react"; export default function App() { return ( Name Role Kate Moore CEO
); } ``` ## Key Changes ### 1. Component Structure **v2:** Separate named imports (`Table`, `TableHeader`, `TableColumn`, `TableBody`, `TableRow`, `TableCell`) **v3:** Single import with dot notation: `Table`, `Table.ScrollContainer`, `Table.Content`, `Table.Header`, `Table.Column`, `Table.Body`, `Table.Row`, `Table.Cell`, `Table.Footer` ### 2. New Wrapper Components - **`Table`** is now the root container (styling wrapper) - **`Table.ScrollContainer`** handles horizontal scrolling with custom scrollbar - **`Table.Content`** is the actual `` element (where `aria-label`, `selectionMode`, `sortDescriptor` etc. go) - **`Table.Footer`** replaces `bottomContent` for pagination and other footer content ### 3. Prop Changes | v2 Prop | v3 Equivalent | Notes | |---------|---------------|-------| | `aria-label` | `Table.Content` `aria-label` | Moved to `Table.Content` | | `selectionMode` | `Table.Content` `selectionMode` | Moved to `Table.Content` | | `selectedKeys` | `Table.Content` `selectedKeys` | Moved to `Table.Content` | | `defaultSelectedKeys` | `Table.Content` `defaultSelectedKeys` | Moved to `Table.Content` | | `onSelectionChange` | `Table.Content` `onSelectionChange` | Moved to `Table.Content` | | `sortDescriptor` | `Table.Content` `sortDescriptor` | Moved to `Table.Content` | | `onSortChange` | `Table.Content` `onSortChange` | Moved to `Table.Content` | | `disabledKeys` | `Table.Content` `disabledKeys` | Moved to `Table.Content` | | `disallowEmptySelection` | `Table.Content` `disallowEmptySelection` | Moved to `Table.Content` | | `selectionBehavior` | `Table.Content` `selectionBehavior` | Moved to `Table.Content` | | `disabledBehavior` | `Table.Content` `disabledBehavior` | Moved to `Table.Content` | | `onRowAction` | `Table.Content` `onRowAction` | Moved to `Table.Content` | | `onCellAction` | `Table.Content` `onCellAction` | Moved to `Table.Content` | | `topContent` | - | Place content inside `Table` before `Table.ScrollContainer` | | `bottomContent` | - | Use `Table.Footer` | | `topContentPlacement` | - | Removed (compose layout directly) | | `bottomContentPlacement` | - | Removed (compose layout directly) | | `color` | - | Removed (use Tailwind CSS) | | `variant` | `Table` `variant` | Changed to `"primary"` (card-style, default) or `"secondary"` (flat) | | `layout` | - | Removed | | `radius` | - | Removed (use Tailwind CSS) | | `shadow` | - | Removed (use Tailwind CSS) | | `isStriped` | - | Removed (use Tailwind CSS) | | `isCompact` | - | Removed (use Tailwind CSS) | | `isHeaderSticky` | - | Removed (use Tailwind CSS `sticky top-0`) | | `fullWidth` | - | Removed (full width by default) | | `removeWrapper` | - | Removed (compose layout directly) | | `hideHeader` | - | Removed (omit `Table.Header` or use CSS) | | `isVirtualized` | - | Use React Aria `Virtualizer` wrapper | | `maxTableHeight` | - | Use CSS or Virtualizer | | `rowHeight` | - | Use `TableLayout` with Virtualizer | | `isKeyboardNavigationDisabled` | - | Removed | | `disableAnimation` | - | Removed | | `classNames` | - | Use `className` on individual compound components | ### 4. Selection with Checkboxes **v2:** Checkboxes auto-rendered by the table when `selectionMode` is set **v3:** Use `Checkbox` with `slot="selection"` explicitly in columns and rows ### 5. Loading and Empty States **v2:** `loadingState`, `loadingContent`, `emptyContent` props on `TableBody` **v3:** `renderEmptyState` prop on `Table.Body`; use `Table.LoadMore` for infinite scroll loading ### 6. Pagination **v2:** `bottomContent` prop on `Table` **v3:** `Table.Footer` compound component ### 7. Column Resizing **v2:** Not built-in **v3:** `Table.ResizableContainer` + `Table.ColumnResizer` compound components ## Migration Examples ### Basic Table ```tsx import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from "@heroui/react";
Name Role Status Kate Moore CEO Active John Doe Developer Active
``` ```tsx import { Table } from "@heroui/react"; Name Role Status Kate Moore CEO Active John Doe Developer Active
```
### Dynamic Rows ```tsx const columns = [ { key: "name", label: "Name" }, { key: "role", label: "Role" }, ]; const rows = [ { key: "1", name: "Kate", role: "CEO" }, { key: "2", name: "John", role: "Developer" }, ]; {(column) => {column.label}} {(item) => ( {(columnKey) => {item[columnKey]}} )}
```
```tsx const columns = [ { id: "name", label: "Name" }, { id: "role", label: "Role" }, ]; const rows = [ { id: "1", name: "Kate", role: "CEO" }, { id: "2", name: "John", role: "Developer" }, ]; {(column) => {column.label}} {(item) => ( {item.name} {item.role} )}
```
### Selection ```tsx const [selectedKeys, setSelectedKeys] = useState(new Set(["1"])); Name Role Kate CEO John Developer
```
```tsx import { Table, Checkbox } from "@heroui/react"; const [selectedKeys, setSelectedKeys] = useState(new Set(["1"])); Name Role Kate CEO John Developer
```
### Sorting ```tsx const [sortDescriptor, setSortDescriptor] = useState({ column: "name", direction: "ascending", }); Name Role {(item) => ( {(columnKey) => {item[columnKey]}} )}
```
```tsx const [sortDescriptor, setSortDescriptor] = useState({ column: "name", direction: "ascending", }); Name Role {(item) => ( {item.name} {item.role} )}
```
### With Pagination (Bottom Content) ```tsx } bottomContentPlacement="outside" > {/* ... */}
```
```tsx {/* Header and Body */} {/* Pagination component */}
```
### Empty State ```tsx {[]} ``` ```tsx (

No rows to display.

)} > {[]}
```
## Styling Changes ### v2: `classNames` Prop ```tsx ``` ### v3: Direct `className` Props ```tsx
Name Value
``` ## Component Anatomy The v3 Table follows this structure: ``` Table (Root container) ├── Table.ScrollContainer (horizontal scroll) │ └── Table.Content ( element, aria-label, selectionMode, etc.) │ ├── Table.Header () │ │ └── Table.Column (, items, renderEmptyState) │ ├── Table.Row () │ │ └── Table.Cell (
, allowsSorting, etc.) │ │ └── Table.ColumnResizer (optional) │ └── Table.Body (
) │ └── Table.LoadMore (optional, infinite scroll) │ └── Table.LoadMoreContent └── Table.Footer (optional, pagination, etc.) ``` ## Item Identity **v2:** React's `key` was used for both list reconciliation and selection state. **v3:** Use `id` on `Table.Row` and `Table.Column` for selection/sort state; keep React's `key` for lists. ## Summary 1. **Imports**: Separate named imports → single `Table` import with dot notation 2. **New Wrappers**: `Table.ScrollContainer` and `Table.Content` wrap the table structure 3. **Props Moved**: `aria-label`, `selectionMode`, `sortDescriptor`, etc. moved from `Table` to `Table.Content` 4. **Bottom Content**: `bottomContent` prop → `Table.Footer` compound component 5. **Top Content**: `topContent` prop → place content inside `Table` before `Table.ScrollContainer` 6. **Selection Checkboxes**: Auto-rendered → explicit `Checkbox` with `slot="selection"` 7. **Empty State**: `emptyContent` prop → `renderEmptyState` on `Table.Body` 8. **Loading**: `loadingState`/`loadingContent` → `Table.LoadMore` for infinite scroll 9. **Column Resizing**: New `Table.ResizableContainer` and `Table.ColumnResizer` 10. **Item Identity**: `key` → `id` on rows and columns 11. **Styling Props Removed**: `color`, `radius`, `shadow`, `isStriped`, `isCompact` → use Tailwind CSS 12. **ClassNames Removed**: Use `className` on individual compound components