Ballmac UI home

Data Grid

A spreadsheet-style grid for thousands of rows: only visible rows are drawn, cells edit in place, headings sort and resize, columns pin, and it follows the ARIA grid keyboard pattern with undo and paste.

Design the sign-in flow
Maya Chen
In progress
Nov 4, 2026
12
Write the billing webhook
Jonas Weber
In review
Nov 6, 2026
9
Migrate the search index
Priya Nair
To do
Nov 12, 2026
20
Accessibility audit
Omar Haddad
In progress
Nov 9, 2026
14
Empty-state illustrations
Maya Chen
Done
Oct 30, 2026
6
Rate limit the public API
Jonas Weber
Blocked
Nov 15, 2026
11
Translate onboarding to Arabic
Layla Aziz
In progress
Nov 18, 2026
16
Dark-mode contrast pass
Priya Nair
To do
Nov 20, 2026
5
Release notes for 2.4
Omar Haddad
Done
Oct 28, 2026
3
Load test the checkout
Jonas Weber
In review
Nov 2, 2026
8
10 rowsEnter to edit · Ctrl+Z to undo

Installation

$ pnpm dlx shadcn@latest add @ballmac/data-grid

Usage

import { DataGrid } from "@/components/ballmac/data-grid"

The full example is in the Code tab above.

Examples

20,000 rows

ORD-10000
Kofi Haddad
Gdańsk
Free
$0
ORD-10001
Elif Okafor
Lisbon
Business
$13,120
ORD-10002
Farid Novak
Helsinki
Team
$2,270
ORD-10003
Farid Kowalski
Nairobi
Team
$1,560
ORD-10004
Kofi Tanaka
Osaka
Business
$13,090
ORD-10005
Farid Lindqvist
Lisbon
Team
$1,890
ORD-10006
Hugo Singh
Nairobi
Free
$0
ORD-10007
Dev Novak
Lima
Team
$1,140
ORD-10008
Hugo Tanaka
Gdańsk
Business
$7,420
ORD-10009
Lena Novak
Toronto
Free
$0
ORD-10010
Chloé Okafor
Lima
Team
$2,220
ORD-10011
Ines Kowalski
Nairobi
Team
$1,380
ORD-10012
Gia Silva
Pune
Business
$12,810
ORD-10013
Chloé Moreau
Gdańsk
Business
$10,050
ORD-10014
Dev Kowalski
Pune
Free
$0
ORD-10015
Jun Silva
Nairobi
Team
$1,250
20,000 rowsOnly the visible rows are drawn

Pinned columns

NOVA
Nova Semiconductor
$412.75
-0.9
8.9M
419.90
408.30
310.5
34.8
Technology
PANE
Pane Software
$288.00
+3.2
6.3M
291.50
279.40
122.3
41.0
Technology
ACME
Acme Industrial
$128.40
+1.8
3.4M
130.10
125.20
48.2
21.3
Industrials
FRGE
Forge Materials
$203.30
-1.7
2.2M
207.60
201.80
41.0
18.9
Materials
LUMN
Lumen Energy
$77.90
+2.6
5M
78.40
75.10
29.9
12.1
Energy
CNPY
Canopy Health
$91.20
-0.3
1.9M
92.40
90.10
22.6
27.7
Health care
HRBR
Harbor Logistics
$54.10
+0.4
1.2M
54.90
53.20
12.7
15.6
Transport
BLOM
Bloom Foods
$36.55
+0.1
4.8M
36.90
36.00
9.4
24.4
Consumer
8 rowsDrag a heading edge to resize

Validation

Design
design@acme.example
14
$5,600
Engineering
eng@acme.example
42
$21,000
Support
support@acme.example
18
$4,300
Finance
finance@acme.example
6
$2,400
People
people@acme.example
5
$1,800
5 rowsClear the email, then press Enter

Controlled with bulk action

Select invoices to act on them

INV-1043
Fabrikam
$9,640
Open
INV-1046
Litware
$5,480
Paid
INV-1041
Northwind
$4,200
Open
INV-1045
Wingtip
$3,115
Open
INV-1042
Contoso Labs
$1,280
Paid
INV-1044
Tailspin
$760
Overdue
6 rows

Loading

No rows

Nothing here yet

When there is something to show, it will appear here.

Could not load

API reference

PropTypeDefault
label*

The grid's name, read by screen readers.

string—
columns*DataGridColumn<T>[]—
data

The rows, when you hold them in state. Pair with `onDataChange`.

T[]—
defaultData

The rows to start with, when the grid holds them.

T[]—
onDataChange

Called with the new rows and the cells that changed, after an edit, a paste or an undo.

(rows: T[], changes: DataGridChange<T>[]) => void—
onCellChange

Called for every cell that changes.

(change: DataGridChange<T>) => void—
getRowId

A stable id for each row. Defaults to `row.id`, then the position.

(row: T, index: number) => string—
height

The height of the scrolling area. Rows beyond it are not drawn until you scroll to them.

number | string420
density

Row height.

"compact" | "default" | "comfortable""default"
selectable

Show a checkbox on each row.

booleanfalse
selectedRowIdsstring[]—
defaultSelectedRowIdsstring[]—
onSelectedRowIdsChange(ids: string[]) => void—
sorting

Sorting, when you hold it in state.

DataGridSort[]—
defaultSortingDataGridSort[]—
onSortingChange(sorting: DataGridSort[]) => void—
search

Show only rows with a cell that contains this text.

string""
dir

The grid reads right to left. Defaults to the page.

TextDirection—
overscan

Rows drawn beyond the visible ones, for smooth scrolling.

number6
footer

Shown in the bar under the grid, beside the row count.

React.ReactNode—

Also accepts the standard attributes of its root element.

Accessibility

KeyAction
Arrow keysMove between cells (left and right swap in right-to-left)
Home / End / Ctrl+Home / Ctrl+EndFirst and last cell of the row or the grid
Page Up / Page DownMove a screenful of rows
Enter / F2Edit the cell; Enter saves and moves down, Tab saves and moves right, Escape cancels
Type a characterStart editing a text, number or date cell with that character
SpaceToggle a checkbox cell or the row checkbox; Shift+Space selects the row
DeleteClear an editable cell
Ctrl+Z / Ctrl+Shift+ZUndo and redo, one edit or paste at a time
Ctrl+C / Ctrl+VCopy the cell; paste cells copied from a spreadsheet
Screen readersrole=grid with aria-rowcount, aria-rowindex, aria-colindex and aria-sort, so virtualized rows keep their place; edits, undo and paste are announced

Quality scorecard

8/8

Every applicable check passes

Measured in Chromium 153.0.8010.12 on October 19, 2026, on version 1.0.0, across 8 examples.

How we measure
  • Passed: Accessibility scan

    No serious or critical issues in light and dark, on a phone and a desktop (axe-core 4.13.0, WCAG 2.1 AA and best-practice rules).

  • Passed: Small screens

    No sideways scrolling at 360, 390 or 1280 px wide.

  • Passed: Reduced motion

    Every endless animation stops when the visitor asks for less motion.

  • Passed: Right to left

    Mirrors correctly in Arabic, Hebrew, Persian and Urdu layouts (0% of elements off the mirror, limit 15%).

  • Passed: Clean console

    No console errors or React hydration errors while loading.

  • Passed: Keyboard

    67 tab stops, each showing a visible change when it takes focus. 10 documented keys.

  • Passed: Loading, empty and error

    Ships loading, empty, error states, each with an example.

  • Passed: Automated tests

    Covered by data-grid.test.tsx.

  • For information: Footprint

    12.4 KB of source (gzip) in 1 file. Installs lucide-react.

Automated checks find a subset of problems and do not replace testing with a screen reader and a keyboard in your own app. What they miss.

Use with AI

Pass columns ({ id, header, accessorKey, type: text | number | boolean | select | date, editable, width, pin }) and rows with data or defaultData. Set height; rows outside it are not drawn. Listen with onDataChange or onCellChange. With the shadcn MCP server set up (guide), ask your agent:

Add the Ballmac UI Data Grid (@ballmac/data-grid) to this project with the shadcn MCP, then use it where it fits.

Use it for

  • Hundreds to tens of thousands of rows people edit like a spreadsheet
  • Admin tools, inventories, budgets and review queues with keyboard-heavy users
  • Wide data that needs pinned columns and resizable headings

Not for

  • A small read-only table; use table
  • Search, filters, column menus and pagination out of the box; use data-table
  • Formulas, merged cells or multi-sheet workbooks

Registry JSON: https://ui.ballmac.com/r/data-grid.json

Credits

Free to use in personal and commercial projects.

Ballmac UI Pro

Need the whole screen, not one piece?

150 premium blocks (dashboards, settings, calendars, mail, shops, onboarding), a private shadcn registry and Figma tokens. Blocks that show data come with loading, empty and error states built in.

Not ready? Try three Pro blocks free

Leave your email and the blocks appear right here, ready to copy. Yours to use in any project.