HeroUI Pro

Data Grid

A full-featured data grid with sorting, selection, column resizing, pinned columns, drag-and-drop row reorder, virtualization, and async loading — built on the HeroUI Table.

Usage

The DataGrid component takes a flat data array, a columns definition, and a getRowId function. It renders a fully accessible table with built-in support for sorting, selection, column resizing, and more.

Column Definitions

Columns are defined as an array of DataGridColumn<T> objects. Each column has an id, a header, and either an accessorKey (to read a value from the row object) or a custom cell renderer.

import type {DataGridColumn} from "@heroui-pro/react";

interface Payment {
  id: string;
  customer: string;
  amount: number;
  status: "succeeded" | "failed";
}

const columns: DataGridColumn<Payment>[] = [
  {
    id: "customer",
    header: "Customer",
    accessorKey: "customer",
    isRowHeader: true,
    allowsSorting: true,
  },
  {
    id: "amount",
    header: "Amount",
    accessorKey: "amount",
    align: "end",
    cell: (item) => `$${item.amount.toFixed(2)}`,
  },
  {
    id: "status",
    header: "Status",
    accessorKey: "status",
  },
];

Custom Cell Rendering

The cell function receives the full row item and column definition. Return any ReactNode:

{
  id: "status",
  header: "Status",
  cell: (item) => (
    <Chip color={item.status === "succeeded" ? "success" : "danger"} size="sm" variant="soft">
      {item.status}
    </Chip>
  ),
}

Custom Header Rendering

The header property accepts a string, a ReactNode, or a render function that receives { sortDirection } for sortable columns:

{
  id: "amount",
  header: ({ sortDirection }) => (
    <span>Amount {sortDirection === "ascending" ? "↑" : sortDirection === "descending" ? "↓" : ""}</span>
  ),
  allowsSorting: true,
}

Row Selection

Enable row selection with selectionMode and showSelectionCheckboxes. Supports both "single" and "multiple" modes with controlled or uncontrolled state.

const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set());

<DataGrid
  aria-label="Users"
  columns={columns}
  data={users}
  getRowId={(item) => item.id}
  selectionMode="multiple"
  showSelectionCheckboxes
  selectedKeys={selectedKeys}
  onSelectionChange={setSelectedKeys}
/>

Sorting

Mark columns as sortable with allowsSorting: true. In uncontrolled mode, the DataGrid sorts data client-side using locale-aware string comparison (or a custom sortFn). For server-side sorting, pass a controlled sortDescriptor and handle onSortChange.

Uncontrolled (client-side)

<DataGrid
  aria-label="Payments"
  columns={columns}
  data={payments}
  getRowId={(item) => item.id}
  defaultSortDescriptor={{ column: "customer", direction: "ascending" }}
/>

Controlled (server-side)

const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>({
  column: "date",
  direction: "descending",
});

<DataGrid
  aria-label="Payments"
  columns={columns}
  data={payments}
  getRowId={(item) => item.id}
  sortDescriptor={sortDescriptor}
  onSortChange={setSortDescriptor}
/>

Custom Sort Function

Provide a sortFn on a column for custom comparison logic:

{
  id: "priority",
  header: "Priority",
  allowsSorting: true,
  sortFn: (a, b) => priorityOrder[a.priority] - priorityOrder[b.priority],
}

Column Resizing

Enable column resizing with allowsColumnResize on the DataGrid and allowsResizing on individual columns. Columns must have minWidth set.

<DataGrid
  aria-label="Payments"
  columns={columns}
  data={payments}
  getRowId={(item) => item.id}
  allowsColumnResize
  onColumnResize={(widths) => console.log("Resizing:", widths)}
  onColumnResizeEnd={(widths) => console.log("Final widths:", widths)}
/>

Pinned Columns

Pin columns to the start or end edge so they stay visible during horizontal scroll. Pinned columns must have a numeric width or minWidth.

const columns: DataGridColumn<Company>[] = [
  {
    id: "name",
    header: "Company",
    pinned: "start",
    minWidth: 160,
    // ...
  },
  // ... scrollable columns ...
  {
    id: "actions",
    header: "",
    pinned: "end",
    width: 50,
    cell: (item) => <RowActions id={item.id} />,
  },
];

Drag and Drop

Enable row reorder with the onReorder callback. The DataGrid provides built-in drag handles, keyboard support (Enter to grab, arrows to move, Enter to drop), and fires the callback with the reordered data array.

const [tasks, setTasks] = useState(initialTasks);

<DataGrid
  aria-label="Backlog"
  columns={columns}
  data={tasks}
  getRowId={(item) => item.id}
  onReorder={(event) => setTasks(event.reorderedData)}
/>

For advanced drag-and-drop scenarios (cross-list, custom drag items), pass dragAndDropHooks directly from RAC's useDragAndDrop.

Editable Cells

Use the cell render function to embed any interactive component — text fields, selects, switches, number steppers, etc.

Empty State

Provide a renderEmptyState function to display a custom empty state when data is empty.

<DataGrid
  aria-label="Projects"
  columns={columns}
  data={[]}
  getRowId={(item) => item.id}
  renderEmptyState={() => (
    <EmptyState size="sm">
      <EmptyState.Header>
        <EmptyState.Media variant="icon">
          <FolderOpen />
        </EmptyState.Media>
        <EmptyState.Title>No Projects Yet</EmptyState.Title>
        <EmptyState.Description>
          Get started by creating your first project.
        </EmptyState.Description>
      </EmptyState.Header>
    </EmptyState>
  )}
/>

Async Loading

Use onLoadMore, isLoadingMore, and loadMoreContent to implement infinite scroll loading. The DataGrid renders a sentinel row that triggers onLoadMore when it scrolls into view.

<DataGrid
  aria-label="Invoices"
  columns={columns}
  data={items}
  getRowId={(item) => item.id}
  scrollContainerClassName="max-h-[400px] overflow-y-auto"
  onLoadMore={hasMore ? handleLoadMore : undefined}
  isLoadingMore={isLoading}
  loadMoreContent={<Spinner size="md" />}
/>

Virtualization

Enable row virtualization for large datasets (1,000+ rows) with the virtualized prop. Only visible rows are rendered to the DOM. You must set rowHeight and headingHeight.

<DataGrid
  virtualized
  aria-label="Product inventory"
  columns={columns}
  data={products}
  getRowId={(item) => item.id}
  contentClassName="h-[600px] min-w-[900px] overflow-auto"
  rowHeight={58}
  headingHeight={37}
/>

Bulk Actions

Combine row selection with an ActionBar to provide bulk operations like export, archive, or delete.

Users

A minimal user directory using the "secondary" variant with accessorKey-only columns and a row action link — no selection, no sorting.

Team Members

A full-featured HR table with controlled sorting, multi-selection, pinned columns, column resizing, column visibility toggling, search, filters, and client-side pagination.

Servers

A server monitoring dashboard with controlled sorting, selection, column visibility toggling, search, status filtering, and rich cell renderers including sparkline charts and circular progress indicators.

CSS Classes

Base Classes

  • .data-grid — Root wrapper. Sets position: relative and width: 100%. Defines --data-grid-selection-column-width and --data-grid-drag-handle-column-width custom properties.

Element Classes

  • .data-grid__selection-column — Narrow <th> for the select-all checkbox. Fixed width from --data-grid-selection-column-width.
  • .data-grid__selection-cell — Narrow <td> for row selection checkboxes. Same fixed width.
  • .data-grid__drag-handle-column — Narrow <th> for the drag handle column. Fixed width from --data-grid-drag-handle-column-width.
  • .data-grid__drag-handle-cell — Narrow <td> for the drag handle. Same fixed width.
  • .data-grid__drag-handle — The grip button inside each row. Styled with cursor: grab and subtle color.
  • .data-grid__sort-icon — Chevron indicator next to sortable column headers. Rotates 180° when descending.
  • .data-grid__empty-state — Centered container for the empty state message. Muted text, vertical padding.

Interactive States

  • Drag handle hover: &:hover / [data-hovered="true"] on .data-grid__drag-handle — text transitions to foreground.
  • Drag handle pressed: &:active / [data-pressed="true"] on .data-grid__drag-handle — cursor changes to grabbing.
  • Drag handle focus: [data-focus-visible="true"] on .data-grid__drag-handle — applies focus ring via status-focused.
  • Row dragging: .table__row[data-dragging="true"] — reduced opacity (0.5).
  • Drop indicator: .react-aria-DropIndicator[data-drop-target] td — accent background line.

Alignment

  • [data-align="end"] — Right-aligns both header and cell content.
  • [data-align="center"] — Center-aligns both header and cell content.

Vertical Alignment

Controlled by the verticalAlign prop via [data-vertical-align] on the root:

  • [data-vertical-align="top"]vertical-align: top (flexbox items-start in virtualized mode).
  • [data-vertical-align="middle"]vertical-align: middle (flexbox items-center in virtualized mode).
  • [data-vertical-align="bottom"]vertical-align: bottom (flexbox items-end in virtualized mode).

Pinned Columns

  • [data-pinned] — Makes the cell position: sticky with z-index: 2.
  • [data-pinned="start"] — Sticky to inset-inline-start.
  • [data-pinned="end"] — Sticky to inset-inline-end.
  • [data-pinned-edge] — Boundary column that shows a separator line when the pinned group detaches from the scrollable content.
  • [data-pinned-start-detached] — Set on root when content has scrolled past the start-pinned columns. Shows separator via ::after.
  • [data-pinned-end-detached] — Set on root when content hasn't scrolled to the end edge. Shows separator via ::after.

CSS Variables

  • --data-grid-selection-column-width — Width of the selection checkbox column (default: 40px).
  • --data-grid-drag-handle-column-width — Width of the drag handle column (default: 32px).

API Reference

DataGrid

The root data grid component. Accepts a generic type parameter T for the row data shape.

PropTypeDefaultDescription
dataT[]Row data array.
columnsDataGridColumn<T>[]Column definitions.
getRowId(item: T) => string | numberExtracts a unique key from each row item.
aria-labelstringAccessible label for the table. Required.
variant"primary" | "secondary""primary"Visual variant passed to the underlying Table.
classNamestringAdditional className for the root wrapper.
contentClassNamestringAdditional className for the inner <table> element (e.g. min-w-[1200px] for horizontal scroll).
scrollContainerClassNamestringAdditional className for the scroll container (e.g. max-h-[400px] overflow-y-auto).
verticalAlign"top" | "middle" | "bottom""middle"Vertical alignment of cell content within each row.
selectionMode"none" | "single" | "multiple""none"Row selection mode.
selectedKeysSelectionControlled selected row keys.
defaultSelectedKeysSelectionDefault selected row keys (uncontrolled).
onSelectionChange(keys: Selection) => voidCallback when selection changes.
selectionBehavior"toggle" | "replace""toggle"Selection interaction model.
showSelectionCheckboxesbooleanfalseAuto-prepend a checkbox column for selection.
sortDescriptorSortDescriptorControlled sort descriptor. When provided, sorting is controlled externally.
defaultSortDescriptorSortDescriptorDefault sort descriptor (uncontrolled).
onSortChange(descriptor: SortDescriptor) => voidCallback when sort changes. Fires in both controlled and uncontrolled modes.
allowsColumnResizebooleanfalseEnable column resizing on columns that opt in.
onColumnResize(widths: Map<string | number, ColumnSize>) => voidCallback during column resize.
onColumnResizeEnd(widths: Map<string | number, ColumnSize>) => voidCallback when resize ends.
onReorder(event: DataGridReorderEvent<T>) => voidConvenience callback for row reorder. Enables built-in drag-and-drop. Mutually exclusive with dragAndDropHooks.
dragAndDropHooksDragAndDropHooksAdvanced RAC drag-and-drop hooks for custom DnD scenarios. Overrides onReorder.
onRowAction(key: string | number) => voidCallback when a row is actioned (e.g. double-click or Enter).
renderEmptyState() => ReactNodeRender function for the empty state when data is empty.
onLoadMore() => voidCallback when the load-more sentinel scrolls into view.
isLoadingMorebooleanfalseWhether more data is currently being fetched.
loadMoreContentReactNodeContent to show inside the load-more sentinel row (e.g. a Spinner).
disabledKeysIterable<string | number>Keys of rows that should be disabled.
virtualizedbooleanfalseEnable row virtualization for large datasets. Requires rowHeight and headingHeight.
rowHeightnumber42Fixed row height in pixels. Required when virtualized is true.
headingHeightnumber36Header row height in pixels. Required when virtualized is true.

DataGridColumn<T>

Column definition object passed to the columns prop.

PropertyTypeDefaultDescription
idstringUnique column identifier. Used as the sort key and RAC column id. Required.
headerReactNode | ((info: { sortDirection?: SortDirection }) => ReactNode)Column header content. String, node, or render function receiving sort info.
accessorKeykeyof T & stringKey on T to read the cell value from. Used for default rendering and sorting.
cell(item: T, column: DataGridColumn<T>) => ReactNodeCustom cell renderer. Receives the row item and column definition.
isRowHeaderbooleanfalseMark this column as the row header (for accessibility).
allowsSortingbooleanfalseAllow this column to be sorted.
sortFn(a: T, b: T) => numberCustom sort comparator. Falls back to locale-aware string comparison.
allowsResizingbooleanAllow this column to be resized. Only effective when allowsColumnResize is true on the DataGrid.
widthColumnSizeInitial/controlled column width (px, %, or fr).
minWidthnumberMinimum column width when resizing.
maxWidthnumberMaximum column width when resizing.
align"start" | "center" | "end""start"Cell text alignment.
headerClassNamestringAdditional className appended to every <th> for this column.
cellClassNamestringAdditional className appended to every <td> for this column.
pinned"start" | "end"Pin this column so it stays visible during horizontal scroll. Uses logical directions (start = left in LTR). Pinned columns must have a numeric width or minWidth.

DataGridReorderEvent<T>

Event object passed to the onReorder callback.

PropertyTypeDescription
keysSet<string | number>The keys that were moved.
target{ key: string | number; dropPosition: "before" | "after" }The target row key and drop position.
reorderedDataT[]The full reordered data array after applying the move.

Type Exports

The following types are re-exported from the DataGrid module for convenience:

TypeOriginDescription
DataGridSelectionreact-aria-components SelectionRepresents a set of selected keys, or "all".
DataGridSortDescriptorreact-aria-components SortDescriptorDescribes the current sort column and direction.
DataGridSortDirectionreact-aria-components SortDirection"ascending" | "descending".
DataGridColumnSizeA number, a numeric string, a percentage string, or a fractional unit string.

On this page