---
title: "Blueprint: Table"
slug: "blueprint-table"
category: "frontend"
tags: ["frontend", "blueprint", "react", "table", "virtualization", "data-grid", "keyboard"]
status: "stable"
last_updated: 2026-08-14
summary: "Use Blueprint Table (formerly Table2) for large virtualized datasets; define columns with ColumnHeaderCell; wrap the app in HotkeysProvider for keyboard shortcuts."
related: ["[[frontend/blueprint]]", "[[frontend/blueprint-overlays]]", "[[frontend/blueprint-theming]]", "[[frontend/react]]", "[[frontend/react-performance]]", "[[frontend/accessibility]]", "[[frontend/forms]]"]
---

## Overview

Blueprint's `Table` component (the unversioned name in Blueprint 6; the same API was called `Table2` in Blueprint 4 and 5) is a virtualized, column-resizable data grid designed for datasets with hundreds to tens of thousands of rows. It renders only visible cells, recycles DOM nodes during scroll, and supports cell renderers, selection models, and inline editing. This page covers how to set up the table correctly, define columns, render cells efficiently, and handle selection without breaking keyboard navigation.

## Wrap the app in `HotkeysProvider` before using any Blueprint table

Blueprint's keyboard shortcuts system requires `HotkeysProvider` at or near the root. Without it, the table's built-in shortcuts (copy selection, move focus with arrow keys, expand selection with Shift) silently fail.

```tsx
import { HotkeysProvider } from "@blueprintjs/core";

function App() {
  return (
    <HotkeysProvider>
      <Router />
    </HotkeysProvider>
  );
}
```

Mount `HotkeysProvider` once. Multiple providers produce keybinding conflicts. If another hotkey library is in use, check for conflicts before mounting Blueprint's provider. The provider renders no DOM; it only wires the keyboard event system.

## Import `@blueprintjs/table/lib/css/table.css` at the entry point

The table's CSS is in a separate package. Missing it produces a table with no borders, no selection highlights, and broken column-resize handles; the JavaScript still works, which makes the bug subtle.

```ts
// main.tsx or app entry
import "normalize.css";
import "@blueprintjs/core/lib/css/blueprint.css";
import "@blueprintjs/icons/lib/css/blueprint-icons.css";
import "@blueprintjs/table/lib/css/table.css";
```

Import order matters: normalize first, then blueprint, then table. Reversing the order lets table styles accidentally override Blueprint core styles. See [[frontend/blueprint]] for the full package list.

## Define columns with `Column` and `ColumnHeaderCell`

Each `Column` declares a header and a cell renderer. `ColumnHeaderCell` supports a name string and a menu renderer for column actions.

```tsx
import { Column, ColumnHeaderCell, Table } from "@blueprintjs/table";

function DataTable({ rows }: { rows: Row[] }) {
  const nameHeader = () => <ColumnHeaderCell name="Display Name" />;
  const nameCell = (rowIndex: number) => (
    <Cell>{rows[rowIndex].displayName}</Cell>
  );

  return (
    <Table numRows={rows.length}>
      <Column name="Name" cellRenderer={nameCell} columnHeaderCellRenderer={nameHeader} />
      <Column name="Email" cellRenderer={(i) => <Cell>{rows[i].email}</Cell>} />
      <Column name="Role" cellRenderer={(i) => <Cell>{rows[i].role}</Cell>} />
    </Table>
  );
}
```

On a project still pinned to Blueprint 5, import `Table2` and `ColumnHeaderCell2` instead; Blueprint 6 keeps both as deprecated aliases of `Table` and `ColumnHeaderCell`, so either name compiles during a migration.

Pass `numRows` as a number. The table uses it to size the scroll container before rendering cells. If `numRows` differs from the actual row count, scroll positions and virtual windowing break. Keep `numRows` in sync with the data array length.

## Use cell renderers for custom formatting; keep them pure

A cell renderer is a function `(rowIndex: number) => JSX.Element`. It is called frequently: on scroll, on resize, and on re-render. Keep it pure and cheap.

```tsx
const statusCell = (rowIndex: number) => {
  const status = rows[rowIndex].status;
  return (
    <Cell intent={status === "error" ? Intent.DANGER : Intent.NONE}>
      <Tag minimal intent={status === "error" ? Intent.DANGER : Intent.SUCCESS}>
        {status}
      </Tag>
    </Cell>
  );
};
```

Avoid fetching data inside a cell renderer. Fetch before the table renders; pass data as props or close over a stable array. Unstable references inside a renderer (inline objects, arrow functions created on every render) cause the table to re-render more cells than necessary. Use `useCallback` for renderers that depend on changing state. See [[frontend/react-performance]] for the memoization rules.

## Configure selection with `selectionModes` and an `onSelection` callback

Table supports four selection modes: none, rows, columns, and cells. Pass the appropriate constant from `SelectionModes`.

```tsx
import { SelectionModes } from "@blueprintjs/table";

<Table
  numRows={rows.length}
  selectionModes={SelectionModes.ROWS_AND_CELLS}
  onSelection={(selections) => {
    const selected = selections.map((r) => rows[r.rows?.[0] ?? 0]);
    setSelectedRows(selected);
  }}
>
```

`SelectionModes.ROWS_ONLY` is common for list-style grids where row-level actions (delete, export) should act on a set of rows. `SelectionModes.NONE` disables interactive selection for read-only tables. Keyboard selection (Shift+Click, Ctrl+A) works automatically once the mode is set; no additional event handling is needed.

## Handle large datasets with row loading and sparse data

For datasets fetched in pages, render a loading cell while data is in flight.

```tsx
const cell = (rowIndex: number) => {
  const row = rows[rowIndex];
  if (!row) return <Cell loading />;
  return <Cell>{row.name}</Cell>;
};
```

The `loading` prop renders a skeleton shimmer. Load rows into the array as they arrive; the table re-renders only the cells that changed. For very large datasets (100k+ rows), keep `numRows` as the total count and fill the array lazily from a windowed fetch. This avoids a DOM with 100k cells on the initial render.

## Related

- [[frontend/blueprint]]
- [[frontend/blueprint-overlays]]
- [[frontend/blueprint-theming]]
- [[frontend/react]]
- [[frontend/react-performance]]
- [[frontend/accessibility]]
