Data grid · ag-grid-style

34 rows
ID
Name
Role
City
Age
Salary
Active
1Ada A.EngineerIstanbul24$60,000
2Linus B.DesignerBerlin31$61,234
3Grace C.PMParis38$62,468
4Alan D.AnalystTokyo45$63,702
5Margaret E.SalesMadrid52$64,936
6Dennis F.EngineerIstanbul59$66,170
7Barbara G.DesignerBerlin26$67,404
8Ken H.PMParis33$68,638
1–8 of 34

Double-click a cell to edit · click headers to sort, Shift-click for a second column · drag edges to resize · filter / select / paginate · 0 selected

The same grid over 100,000 rows with no pages, a filter under each column, headers to drag, pin and hide — only the rows in view are in the DOM, so scroll, sort and filter cost what they cost on ten:

100000 rows
ID
Name
Role
City
Age
Salary
Active
1Ada A.EngineerIstanbul24$60,000
2Linus B.PMTokyo31$61,234
3Grace C.SalesBerlin38$62,468
4Alan D.DesignerMadrid45$63,702
5Margaret E.AnalystParis52$64,936
6Dennis F.EngineerIstanbul59$66,170
7Barbara G.PMTokyo26$67,404
8Ken H.SalesBerlin33$68,638
9Edsger I.DesignerMadrid40$69,872
10Donald J.AnalystParis47$71,106
11Ada K.EngineerIstanbul54$72,340
12Linus L.PMTokyo61$73,574
13Grace M.SalesBerlin28$74,808
14Alan N.DesignerMadrid35$76,042
15Margaret O.AnalystParis42$77,276
16Dennis P.EngineerIstanbul49$78,510
17Barbara Q.PMTokyo56$79,744
18Ken R.SalesBerlin63$80,978
19Edsger S.DesignerMadrid30$82,212
20Donald T.AnalystParis37$83,446
21Ada U.EngineerIstanbul44$84,680
22Linus V.PMTokyo51$85,914
23Grace W.SalesBerlin58$87,148
24Alan X.DesignerMadrid25$88,382
25Margaret Y.AnalystParis32$89,616
26Dennis Z.EngineerIstanbul39$90,850
27Barbara A.PMTokyo46$92,084
28Ken B.SalesBerlin53$93,318
29Edsger C.DesignerMadrid60$94,552
30Donald D.AnalystParis27$95,786
31Ada E.EngineerIstanbul34$97,020
32Linus F.PMTokyo41$98,254
33Grace G.SalesBerlin48$99,488
34Alan H.DesignerMadrid55$100,722
35Margaret I.AnalystParis62$101,956
36Dennis J.EngineerIstanbul29$103,190

And over a source — here a function over the same rows, where a server would be — with grouping: drag a header onto the panel, or pick "Group by" from its ⋮. Every level is one question, paged on its own:

Drag a column header here to group by it0 rows · asking…
ID
Name
Role
City
Age
Salary
Active
asking…
0 rows

Usage

JavaScript
import { DataGrid } from "./components/grid.js"; const columns = [  { field: "id",     header: "ID",     width: 64,  align: "right" },  { field: "name",   header: "Name",   width: 180, editable: true },  { field: "salary", header: "Salary", align: "right", type: "number",    editable: true, format: (v) => "$" + v.toLocaleString() },  { field: "active", header: "Active", format: (v) => (v ? "✓" : "—") },]; DataGrid({  rows: people,            // array, or a signal/getter for live data  columns,  selectable: true,        // row checkboxes  pageSize: 8,  onSelectionChange: (rows) => selCount.set(rows.length),  onCellEdit: (e) => toast(`${e.field}: ${e.oldValue}${e.value}`),}) // `rowEdit`: entering a cell opens every editable cell of its row; Tab// walks them, Enter or leaving the row saves them as one onRowEdit// ({row, changes}), Escape drops them. Ctrl+Z takes an edit back.DataGrid({ rows: people, columns, rowEdit: true, onRowEdit: (e) => api.save(e.row, e.changes) }) // No pages: the table holds the rows in view and a spacer for the rest,// however many there are. `height` fixes the viewport; rows are one// height (measured, or `rowHeight`).// `filters` puts a box under every column: text "contains", a number or// date box that takes `>10`, `<=5`, `10..20`, `2024-03`, a list for// a column of options. `filter: true` on one column does it for that one.// `layout` lets the reader drag a header into another place, pin it left// or right or hide it from the ⋮ on it, and bring columns back from the// Columns button; `columnState` is that layout as a signal, to keep.// `csv` puts a CSV button beside Columns; `apiRef.csv()` is the same rows// as a string, `apiRef.download("rows.csv")` hands the reader the file.DataGrid({ rows: crowd, columns, pageSize: 0, height: 320, filters: true, layout: true, csv: true }) // A source: the grid asks for every page — {page, pageSize, sort, filters,// quick, edge, groupBy, keys} — and shows what comes back. With `groupable`// a header dragged onto the panel groups the rows: every level is one// question with `groupBy` and the path down to it as `keys`, answered// with {key, count, ...aggregates} or, at the last level, the rows.DataGrid({ source: (ask) => fetch("/rows?" + toQuery(ask)).then((r) => r.json()),  columns, pageSize: 10, filters: true, groupable: true, jump: true }) // Every word the grid says is in `strings` (STRINGS has the English):DataGrid({ rows, columns, strings: { filter: "Süz…", noData: "Kayıt yok",  rows: (n) => `${n} kayıt`, range: (a, b, n) => `${n} kayıttan ${a}${b}` } }) // The sort, the filter and the page are signals — hand them in to hold// them, or read them off apiRef. With `total`, the rows are taken as// already sorted, filtered and cut to the page by whoever made them: the// grid shows what it is given and says what the reader asked for.const sort = signal(null), filter = signal(""), page = signal(1);const res = resource(() => (sort(), filter(), page(),  fetch(`/api/people?${new URLSearchParams({ sort: JSON.stringify(sort()), q: filter(), page: page() })}`).then((r) => r.json())));DataGrid({ rows: () => res.data()?.rows || [], total: () => res.data()?.total, sort, filter, page, columns })