Admin data tables and card surfaces under one set of chrome.
The grid block is the admin list surface: a table or a card view, a toolbar, a quick filter, column visibility, multi-select with a batch delete, a row menu, and an add-and-edit form that opens over it. It is the largest of the blocks, and the only one that composes the others — filter supplies its criteria, form supplies its editor, and export writes the file when someone downloads what they are looking at.
Search, sort a column, hide one from the toolbar menu, select rows and delete them, or edit one from its row menu.
| Surface | Renders | Loads more by | Use for |
|---|---|---|---|
AgGrid | data table | infinite row model | unbounded admin |
TanstackGrid | data table | pagination | bounded admin |
GridLegendList | virtualized card grid | reaching the end | admin card views |
GridScrollList | plain card grid | a Load more button | public listings |
The choice is about who is reading, not how many rows there are. Both data tables sit under the same chrome — the same toolbar, selection bar, delete dialog and form modal — and differ only in what the library underneath gives you. AG Grid brings row virtualization, an infinite row model, column resize, reorder and pin, and a cell context menu. TanStack is headless, so the cells are ordinary React under your own tokens, which is what the table above uses.
A public listing is the one case where the choice is forced. Virtualization keeps only the visible cards in the DOM, and a crawler reads the DOM; a card grid on a storefront therefore keeps every loaded card and pages with a real link, so the listing stays crawlable and the footer stays reachable.
<GridConfigProvider grid="products">
<FiltersProvider filters={filters} sorts={sorts}>
<ProductsTable />
</FiltersProvider>
</GridConfigProvider>GridConfigProvider is the top: it names the grid, and everything keyed to that name — column visibility, widths, the chosen view, the filter state — persists under it. Inside, the table builds its own binding with useTanstackGrid or useAgGridState and hands it to GridProvider, which is what the toolbar, the selection bar and the delete dialog all read.
Selection deliberately does not belong to the table library. It is held above, so the same selection bar and the same batch delete work whichever surface is rendering.
The table above holds its rows in local state, which is what a bounded list wants. An unbounded one hands the block a cursor-paged feed instead, and the two card surfaces take exactly the shape that feed returns, so swapping a virtualized card grid for a crawlable one is a single line.
Columns are native to the surface — TanStack ColumnDefs here, AG Grid column definitions there. There is no neutral column type across the two, because a shared abstraction over both would be poorer than either. A column can also be authored as configuration rather than code, which is how a tenant reorders, renames or hides columns without a deploy; a configured column picks from the built-in renderers, and a bespoke cell stays in the code that owns it.