Data grid · ag-grid-style
ID | Name | Role | City | Age | Salary | Active | |
|---|---|---|---|---|---|---|---|
| 1 | Ada A. | Engineer | Istanbul | 24 | $60,000 | — | |
| 2 | Linus B. | Designer | Berlin | 31 | $61,234 | ✓ | |
| 3 | Grace C. | PM | Paris | 38 | $62,468 | ✓ | |
| 4 | Alan D. | Analyst | Tokyo | 45 | $63,702 | — | |
| 5 | Margaret E. | Sales | Madrid | 52 | $64,936 | ✓ | |
| 6 | Dennis F. | Engineer | Istanbul | 59 | $66,170 | ✓ | |
| 7 | Barbara G. | Designer | Berlin | 26 | $67,404 | — | |
| 8 | Ken H. | PM | Paris | 33 | $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:
ID | Name | Role | City | Age | Salary | Active |
|---|---|---|---|---|---|---|
| 1 | Ada A. | Engineer | Istanbul | 24 | $60,000 | — |
| 2 | Linus B. | PM | Tokyo | 31 | $61,234 | ✓ |
| 3 | Grace C. | Sales | Berlin | 38 | $62,468 | ✓ |
| 4 | Alan D. | Designer | Madrid | 45 | $63,702 | — |
| 5 | Margaret E. | Analyst | Paris | 52 | $64,936 | ✓ |
| 6 | Dennis F. | Engineer | Istanbul | 59 | $66,170 | ✓ |
| 7 | Barbara G. | PM | Tokyo | 26 | $67,404 | — |
| 8 | Ken H. | Sales | Berlin | 33 | $68,638 | ✓ |
| 9 | Edsger I. | Designer | Madrid | 40 | $69,872 | ✓ |
| 10 | Donald J. | Analyst | Paris | 47 | $71,106 | — |
| 11 | Ada K. | Engineer | Istanbul | 54 | $72,340 | ✓ |
| 12 | Linus L. | PM | Tokyo | 61 | $73,574 | ✓ |
| 13 | Grace M. | Sales | Berlin | 28 | $74,808 | — |
| 14 | Alan N. | Designer | Madrid | 35 | $76,042 | ✓ |
| 15 | Margaret O. | Analyst | Paris | 42 | $77,276 | ✓ |
| 16 | Dennis P. | Engineer | Istanbul | 49 | $78,510 | — |
| 17 | Barbara Q. | PM | Tokyo | 56 | $79,744 | ✓ |
| 18 | Ken R. | Sales | Berlin | 63 | $80,978 | ✓ |
| 19 | Edsger S. | Designer | Madrid | 30 | $82,212 | — |
| 20 | Donald T. | Analyst | Paris | 37 | $83,446 | ✓ |
| 21 | Ada U. | Engineer | Istanbul | 44 | $84,680 | ✓ |
| 22 | Linus V. | PM | Tokyo | 51 | $85,914 | — |
| 23 | Grace W. | Sales | Berlin | 58 | $87,148 | ✓ |
| 24 | Alan X. | Designer | Madrid | 25 | $88,382 | ✓ |
| 25 | Margaret Y. | Analyst | Paris | 32 | $89,616 | — |
| 26 | Dennis Z. | Engineer | Istanbul | 39 | $90,850 | ✓ |
| 27 | Barbara A. | PM | Tokyo | 46 | $92,084 | ✓ |
| 28 | Ken B. | Sales | Berlin | 53 | $93,318 | — |
| 29 | Edsger C. | Designer | Madrid | 60 | $94,552 | ✓ |
| 30 | Donald D. | Analyst | Paris | 27 | $95,786 | ✓ |
| 31 | Ada E. | Engineer | Istanbul | 34 | $97,020 | — |
| 32 | Linus F. | PM | Tokyo | 41 | $98,254 | ✓ |
| 33 | Grace G. | Sales | Berlin | 48 | $99,488 | ✓ |
| 34 | Alan H. | Designer | Madrid | 55 | $100,722 | — |
| 35 | Margaret I. | Analyst | Paris | 62 | $101,956 | ✓ |
| 36 | Dennis J. | Engineer | Istanbul | 29 | $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
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 })