gridtables
v0.1.0
Published
GridTables — a modern, hook-first, spreadsheet-grade React data grid. Keyboard-native editing, Excel copy/paste, undo, virtualized rows, grid/list/cards views. Zero runtime dependencies.
Downloads
164
Maintainers
Readme
GridTables
A modern, hook-first, spreadsheet-grade React data grid. Keyboard-native editing, Excel copy/paste, undo, optimistic saves, virtualized rows, and three view modes (Grid / List / Cards) — with zero runtime dependencies.
npm install gridtables'use client';
import { useGridTable, GridTable } from 'gridtables';
import 'gridtables/styles.css';
function Properties({ rows }) {
const grid = useGridTable({
data: rows,
columns: [
{ key: 'title', label: 'Property', pinned: true, width: 240 },
{ key: 'value', label: 'Value', type: 'money', width: 120 },
{ key: 'roi', label: 'ROI', type: 'pct', width: 100 },
{ key: 'vacant', label: 'Vacant', type: 'bool', width: 90 },
],
onSaveCells: async (edits) => {
await api.save(edits); // throw to revert the whole batch
},
});
return <GridTable grid={grid} height="60vh" />;
}One hook creates the instance; one component renders it. The grid object is the imperative handle — call grid.setSearch('…'), grid.undo(), grid.applyEdits([...]), grid.setView('cards') from anywhere. The component calling useGridTable never re-renders on grid state changes; subscribe to slices where you need them:
const selected = grid.useGridState((s) => s.selectedRows.size);Features
- Spreadsheet editing — click or type to edit,
Entersaves + moves down,Tabmoves right,Esccancels, booleans toggle in place - Range selection —
Shift+arrows /Shift+click,Deleteclears,Ctrl+Dfills down - Excel round-trip —
Ctrl+Ccopies the selection as TSV,Ctrl+Vpastes a block back, types coerced per column - Undo —
Ctrl+Zwalks the edit history; every batch (paste/fill/clear) is one undo step and re-persists through your save handler - Optimistic saves —
onSaveCells(edits, meta)gets every change; cells show saving → saved states, throw to revert with an error state, or return per-cell errors for partial failure - Virtualized rows with sticky header + pinned column (pad-row technique,
position: stickykeeps working) - Toolbar chrome — search, sort chips, column show/hide, field-set presets, density, fullscreen, keyboard-shortcut cheatsheet
- Row selection with floating bulk-actions bar
- Aggregation footer — Σ sum, average, count per column
- Grid / List / Cards views from the same instance
- Custom cells — per-column
cell/editorrenderers, or register whole cell types withdefineCellType - SSR-safe (
'use client'built in), React 18 & 19
Column types
Built-in: text · int · number · money · area · pct · bool · enum · tags · date · readonly · images · computed
{
key: 'status', label: 'Status', type: 'enum',
options: [{ label: 'Rented', value: 'rented' }, { label: 'Vacant', value: 'vacant' }],
cell: ({ value }) => <span className="gt-pill" data-tone="g">{value}</span>, // custom display
}Custom cell types:
import { defineCellType } from 'gridtables';
const rating = defineCellType({
name: 'rating',
align: 'center',
cell: ({ value }) => '★'.repeat(Number(value) || 0),
editor: (ctx) => <MyRatingEditor ctx={ctx} />, // ctx.commit(v, 'down') / ctx.cancel()
parse: (raw) => Math.min(5, Number(raw) || 0), // clipboard coercion
});
useGridTable({ cellTypes: { rating }, ... });Save pipeline
Everything funnels through one callback — single edits, paste, fill-down, clear, bulk actions, undo:
onSaveCells: async (edits, meta) => {
// meta.source: 'edit' | 'paste' | 'fill' | 'clear' | 'undo' | 'api'
const results = await Promise.allSettled(edits.map(saveOne));
return results.map((r, i) =>
r.status === 'rejected'
? { rowId: edits[i].rowId, colKey: edits[i].colKey, error: String(r.reason) }
: { rowId: edits[i].rowId, colKey: edits[i].colKey }
);
}data stays controlled by you (SWR/React Query friendly). Edits live in an optimistic overlay; when your refreshed data prop contains the saved values, the overlay prunes itself.
Theming
All styling is plain CSS driven by --gt-* custom properties on .gt-root:
.my-theme.gt-root {
--gt-accent: #7c3aed;
--gt-font: 'Inter', sans-serif;
--gt-radius: 8px;
--gt-surface: #111418; /* dark mode: override the neutral scale too */
}State hooks for CSS: [data-selected], [data-active], [data-status="saving|saved|error"], [data-pinned], [data-sorted="asc|desc"], [data-density="compact|roomy"].
Next.js
Works out of the box in the App Router — the package ships with 'use client' banners, so you can pass grid around Client Components freely.
import 'gridtables/styles.css'; // e.g. in app/layout.tsx or the page's client componentIf you consume the package via a file: symlink during development, add transpilePackages: ['gridtables'] to next.config and make sure only one React copy resolves (installing the packed .tgz avoids this class of problem entirely).
Keyboard reference
| Action | Keys |
| --- | --- |
| Move between cells | ↑ ↓ ← → |
| Start editing | Enter / F2 / just type |
| Save, move down / right | Enter / Tab |
| Cancel edit | Esc |
| Select a range | Shift+arrows, Shift+click |
| Select the whole page | Ctrl/⌘ A |
| Copy / paste (Excel) | Ctrl/⌘ C / Ctrl/⌘ V |
| Fill range down | Ctrl/⌘ D |
| Clear cells | Delete |
| Undo | Ctrl/⌘ Z |
Development
npm install
npm run dev # Vite playground on :5199
npm test # vitest (core engine)
npm run typecheck
npm run build # tsup → dist (esm + cjs + d.ts + css)License
MIT
