Overview

Table from @blueprintjs/table is a virtualized, resizable grid with selection, copy, and inline editing. It renders only the cells in the viewport. In Blueprint 6 it is the unversioned Table (the Table2 API from v4 and v5); Table2 stays as a deprecated alias. Package setup is in blueprint.

Import the table stylesheet

The table’s CSS ships in @blueprintjs/table/lib/css/table.css, after the core stylesheet. Without it the grid has no borders, selection highlight, or resize handles, while the JavaScript still works.

Define columns with renderers

Each Column takes a cellRenderer returning a Cell, and optionally a columnHeaderCellRenderer.

import { Cell, Column, ColumnHeaderCell, Table } from "@blueprintjs/table";
 
function DataTable({ rows }: { rows: Row[] }) {
  return (
    <Table numRows={rows.length} cellRendererDependencies={[rows]}>
      <Column
        name="Name"
        cellRenderer={(i) => <Cell>{rows[i].displayName}</Cell>}
        columnHeaderCellRenderer={() => <ColumnHeaderCell name="Display name" />}
      />
      <Column name="Email" cellRenderer={(i) => <Cell>{rows[i].email}</Cell>} />
    </Table>
  );
}

On Blueprint 5, import Table2 and ColumnHeaderCell2. Blueprint 6 removed ColumnHeaderCell2 and the other *2 header and format aliases, so migrate those names.

Keep numRows equal to the data length

The table sizes its scroll area from numRows. A mismatch breaks scroll position and windowing. For paged data, set numRows to the total count and fill the array as pages arrive.

Tell the table when cell data changes

Pass cellRendererDependencies, a dependency list compared with shallow equality. When any element changes, the table invalidates the grid and re-renders all cells. List the data array and any value a renderer reads.

Keep renderers pure and cheap

A renderer runs on scroll, resize, and re-render. Fetch data before rendering, close over a stable array, and use useCallback for renderers that depend on changing state; see react-performance. Show a skeleton for rows still loading with <Cell loading />.

Choose selection modes explicitly

selectionModes takes one of SelectionModes.ALL, ROWS_AND_CELLS, COLUMNS_AND_CELLS, ROWS_ONLY, COLUMNS_ONLY, or NONE. Use ROWS_ONLY for list-style grids with row actions and NONE for read-only tables. onSelection receives Region[]; each region has rows and cols intervals as [start, end], inclusive.

<Table
  numRows={rows.length}
  selectionModes={SelectionModes.ROWS_ONLY}
  onSelection={(regions) =>
    setSelected(regions.flatMap((r) => (r.rows ? rows.slice(r.rows[0], r.rows[1] + 1) : [])))
  }
/>

Keyboard selection and copy work once the mode is set. Provide getCellClipboardData when the copied text should differ from what the cell renders.

Test keyboard use

Verify arrow-key focus movement, Shift-extended selection, and copy after any change to renderers or selection props. Table hotkeys do not require a HotkeysProvider; without one they only miss the hotkeys help dialog. See accessibility for the broader checklist.