@tanstack/react-table vs AG Grid vs react-data-grid: Data Tables in 2026
TL;DR
@tanstack/react-table is the right choice for most React applications — headless, composable, and framework-agnostic, it lets you build exactly the table you need with no UI opinions. AG Grid Community is the right choice when you need Excel-like features (inline editing, copy-paste, pivot tables) and are willing to accept its size. react-data-grid hits the sweet spot for spreadsheet-style tables with a smaller footprint than AG Grid.
Key Takeaways
- @tanstack/react-table: ~1.6M weekly downloads — headless table logic, you supply the UI
- AG Grid Community: ~1.2M weekly downloads — most feature-complete, enterprise-grade, larger bundle
- react-data-grid: ~500K weekly downloads — spreadsheet-style cells, inline editing, virtual rows
- All three support virtualization for large datasets (100K+ rows)
- TanStack Table wins for custom UI and integration with the TanStack ecosystem
- AG Grid wins for Excel-like functionality (pivot, aggregation, inline editing at scale)
- react-data-grid wins for spreadsheet-style interfaces at moderate complexity
Download Trends
| Package | Weekly Downloads | License | Bundle Size |
|---|---|---|---|
@tanstack/react-table | ~1.6M | MIT | ~45KB |
ag-grid-community | ~1.2M | MIT (Community) | ~320KB |
ag-grid-react | ~1.1M | MIT | ~10KB (wrapper) |
react-data-grid | ~500K | MIT | ~90KB |
@tanstack/react-table
TanStack Table (v8) is a headless table utility — it provides all the logic for sorting, filtering, pagination, grouping, and column resizing, but zero markup or styling:
import {
createColumnHelper,
flexRender,
getCoreRowModel,
getSortedRowModel,
getFilteredRowModel,
getPaginationRowModel,
useReactTable,
type SortingState,
} from "@tanstack/react-table"
import { useState } from "react"
type Package = {
name: string
version: string
downloads: number
size: number
license: string
}
const columnHelper = createColumnHelper<Package>()
const columns = [
columnHelper.accessor("name", {
header: "Package",
cell: (info) => (
<a href={`/packages/${info.getValue()}`} className="font-mono text-blue-500">
{info.getValue()}
</a>
),
}),
columnHelper.accessor("downloads", {
header: "Weekly Downloads",
cell: (info) => info.getValue().toLocaleString(),
sortingFn: "basic",
}),
columnHelper.accessor("size", {
header: "Bundle Size",
cell: (info) => `${(info.getValue() / 1024).toFixed(1)}KB`,
}),
columnHelper.accessor("license", {
header: "License",
filterFn: "equals",
}),
]
function PackageTable({ data }: { data: Package[] }) {
const [sorting, setSorting] = useState<SortingState>([])
const [globalFilter, setGlobalFilter] = useState("")
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
state: { sorting, globalFilter },
onSortingChange: setSorting,
onGlobalFilterChange: setGlobalFilter,
initialState: { pagination: { pageSize: 25 } },
})
return (
<div>
<input
value={globalFilter}
onChange={(e) => setGlobalFilter(e.target.value)}
placeholder="Search packages..."
className="border rounded px-3 py-2 mb-4"
/>
<table className="w-full border-collapse">
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
className="text-left p-2 border-b bg-gray-50 cursor-pointer select-none"
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{ asc: " ↑", desc: " ↓" }[header.column.getIsSorted() as string] ?? ""}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id} className="hover:bg-gray-50">
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="p-2 border-b">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
{/* Pagination: */}
<div className="flex gap-2 mt-4">
<button
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</button>
<span>Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}</span>
<button
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</button>
</div>
</div>
)
}
TanStack Table + Virtual rows (100K rows):
import { useVirtualizer } from "@tanstack/react-virtual"
function VirtualTable({ data }: { data: Package[] }) {
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
const { rows } = table.getRowModel()
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 35,
overscan: 20,
})
return (
<div ref={parentRef} style={{ height: 600, overflow: "auto" }}>
<table>
<thead>{/* Headers */}</thead>
<tbody style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map((virtualRow) => {
const row = rows[virtualRow.index]
return (
<tr
key={row.id}
ref={virtualizer.measureElement}
data-index={virtualRow.index}
style={{ position: "absolute", transform: `translateY(${virtualRow.start}px)` }}
>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
))}
</tr>
)
})}
</tbody>
</table>
</div>
)
}
AG Grid
AG Grid Community is the most feature-complete data grid available in React — it's the closest thing to Microsoft Excel in a browser:
import { AgGridReact } from "ag-grid-react"
import { ColDef, GridReadyEvent, CellValueChangedEvent } from "ag-grid-community"
import "ag-grid-community/styles/ag-grid.css"
import "ag-grid-community/styles/ag-theme-quartz.css"
const columnDefs: ColDef[] = [
{ field: "name", headerName: "Package", filter: true, sortable: true, pinned: "left" },
{
field: "downloads",
headerName: "Weekly Downloads",
sortable: true,
filter: "agNumberColumnFilter",
valueFormatter: (p) => p.value.toLocaleString(),
cellStyle: { textAlign: "right" },
},
{
field: "size",
headerName: "Bundle Size",
editable: true, // Inline editing!
valueFormatter: (p) => `${(p.value / 1024).toFixed(1)}KB`,
},
{ field: "license", filter: true, floatingFilter: true },
]
function AGGridTable({ rowData }: { rowData: Package[] }) {
const onCellValueChanged = (event: CellValueChangedEvent) => {
console.log(`${event.colDef.field} changed: ${event.oldValue} → ${event.newValue}`)
// Persist the edit
}
return (
<div className="ag-theme-quartz" style={{ height: 500, width: "100%" }}>
<AgGridReact
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={{ resizable: true, sortable: true }}
pagination={true}
paginationPageSize={50}
onCellValueChanged={onCellValueChanged}
// Virtualization built-in:
rowVirtualizationThreshold={500}
// Row selection:
rowSelection="multiple"
// Excel-style copy/paste:
enableCellTextSelection={true}
// Column groups:
suppressColumnVirtualisation={false}
/>
</div>
)
}
AG Grid Enterprise features (paid license):
- Pivot tables and row grouping
- Server-side row model (infinite scroll from API)
- Clipboard integration (Excel copy-paste)
- Master/detail rows
- Set filters
- Integrated charts
AG Grid Community limitations vs Enterprise:
// Community is free and includes:
// - Sorting, filtering, pagination
// - Inline editing (basic)
// - Row drag-and-drop
// - Themes
// Enterprise adds ($$$ license):
// - groupRowsByColumn (grouping + aggregation)
// - serverSideRowModel
// - setFilter (multi-select filter)
// - masterDetail
// - clipboard (Ctrl+C/V like Excel)
// - rangeSelection (Excel-style cell range selection)
react-data-grid
react-data-grid (by Adazzle) is a spreadsheet-focused grid optimized for inline editing:
import DataGrid, { Column, textEditor, SelectCellFormatter } from "react-data-grid"
import "react-data-grid/lib/styles.css"
type Row = { id: number; package: string; version: string; downloads: number }
const columns: Column<Row>[] = [
{ key: "id", name: "ID", width: 60, frozen: true },
{
key: "package",
name: "Package",
editor: textEditor, // Inline text editing
frozen: true,
},
{ key: "version", name: "Version", editor: textEditor },
{
key: "downloads",
name: "Downloads",
renderCell: ({ row }) => row.downloads.toLocaleString(),
},
]
function SpreadsheetGrid() {
const [rows, setRows] = useState<Row[]>(data)
return (
<DataGrid
columns={columns}
rows={rows}
onRowsChange={setRows} // Called when cells are edited
rowKeyGetter={(row) => row.id}
// Virtual scrolling built-in — handles 100K+ rows:
className="rdg-light"
style={{ height: 500 }}
/>
)
}
react-data-grid is smaller than AG Grid and specifically optimized for spreadsheet-style interfaces. The inline editing model (onRowsChange) is elegant — you receive the updated rows array and decide how to persist.
Feature Comparison
| Feature | @tanstack/react-table | AG Grid Community | react-data-grid |
|---|---|---|---|
| Headless | ✅ | ❌ | ❌ |
| Bundle size | ~45KB | ~320KB | ~90KB |
| Built-in UI | ❌ | ✅ | ✅ |
| Virtual scrolling | ✅ (via @tanstack/virtual) | ✅ Built-in | ✅ Built-in |
| Inline cell editing | ❌ DIY | ✅ | ✅ Built-in |
| Column pinning | ✅ | ✅ | ✅ |
| Column resizing | ✅ | ✅ | ✅ |
| Row grouping | ✅ (logic only) | ✅ | ❌ |
| Pivot tables | ❌ | ✅ Enterprise | ❌ |
| Excel copy-paste | ❌ | ✅ Enterprise | ✅ Community |
| Tree data | ✅ | ✅ | ❌ |
| Server-side pagination | ✅ | ✅ Enterprise | ❌ |
| TypeScript | ✅ | ✅ | ✅ |
| Framework agnostic | ✅ | ❌ React/Angular/Vue | ❌ React-only |
When to Use Each
Choose @tanstack/react-table if:
- You're using shadcn/ui or Tailwind (headless = full design control)
- You want framework-agnostic table logic (Vue, Solid, Angular available)
- The table is part of a larger product UI — not a standalone data tool
- You're already using TanStack Query or Router
Choose AG Grid Community if:
- Users need Excel-like experience (keyboard navigation, inline editing, clipboard)
- You have 100K+ rows and need industrial-strength virtualization
- Column filtering, floating filters, and aggregation are requirements
- Enterprise features aren't needed (or you'll pay for the license)
Choose react-data-grid if:
- You need spreadsheet-style cell editing without the AG Grid bundle size
- Excel copy-paste (Ctrl+C/V) is required without Enterprise license
- Clean row-based inline editing model (
onRowsChange) fits your data flow
Methodology
Download data from npm registry (weekly average, February 2026). Bundle sizes from bundlephobia. Feature comparison based on official documentation for @tanstack/react-table v8.x, ag-grid-community v31.x, and react-data-grid v7.x.