Skip to content

Data table comparison ​

How @rozie-ui/data-table compares to the existing data-table / data-grid libraries across the six frameworks. The data table is the densest, most-requested UI surface there is. Like the listbox and slider it builds on a single framework-agnostic state engine (@tanstack/table-core), but unlike them it does so without the per-framework adapter every other TanStack consumer ships. Rozie wires table-core to all six reactivity systems directly and ships the same <DataTable> + <Column> to each: the same props, two-way state slices, events, slots, and handle.

Research snapshot: 2026-08-10. The data-grid landscape moves quickly; treat the library names, framework coverage, and feature columns as of that date.

The libraries at a glance ​

FrameworkRepresentative option(s)ShapeHeadlessAdapter per frameworkNotes
ReactTanStack Table (@tanstack/react-table), AG Grid React, MUI DataGridhooks / components✅ (TanStack)✅ separate adapterDeepest ecosystem. TanStack Table is the headless gold standard — but @tanstack/react-table is a React-specific adapter over table-core; AG Grid / MUI are styled, batteries-included grids.
Vue@tanstack/vue-table, PrimeVue DataTable, Element Plus, Vuetifycomponents✅ (TanStack)✅ separate adapterTanStack's Vue adapter, plus several styled component grids. PrimeVue's is the closest "declarative <Column>" surface.
Svelte@tanstack/svelte-table, Svelte Headless Tableactions / stores✅✅ separate adapterA separate adapter again; Svelte Headless Table is a community alternative with its own mental model.
Solid@tanstack/solid-tablecomponents✅✅ separate adapterTanStack's Solid adapter; little else first-party.
Angular@tanstack/angular-table, AG Grid Angular, Angular Material MatTablecomponents / directives⚠️✅ separate adapterTanStack's Angular adapter is newer; MatTable is a styled Material component (not headless behaviour you re-skin); AG Grid is the enterprise default.
Lit / web components(none headless)—❌—No headless data table primitive. You hand-roll the row model + ARIA, or wrap AG Grid's vanilla build yourself.
@rozie-ui/data-table@rozie-ui/data-table-*a component✅❌ no adapterSame API on all six: props, twelve two-way slices, events, <Column> API, slots, handle. Built on @tanstack/table-core directly, with no @tanstack/<fw>-table adapter in any leaf.

On its home framework each of these is a solid pick, and Rozie does not claim to out-feature AG Grid on enterprise grids or TanStack Table on React. The case for Rozie is consistency, coverage, and the no-adapter foundation: TanStack ships a separate adapter (with a separate API surface and release cadence) per framework; AG Grid / MUI / PrimeVue are framework-specific styled grids; Lit / web components have nothing headless at all; and Angular's first-party MatTable is styled-only. Rozie gives them all the same <DataTable>, sharing the exact table-core engine TanStack itself uses.

Headless engine, no adapter: the foundation ​

The deepest design choice is what sits between the framework and the row model. Two camps:

  • Adapter-per-framework (@tanstack/react-table, @tanstack/vue-table, @tanstack/svelte-table, …): each framework gets its own thin reactive adapter over the shared @tanstack/table-core. Idiomatic on its home framework, at the cost of a separate package, a separate API surface (hooks vs composables vs stores vs signals), and separate per-framework documentation, all of which can drift.
  • Styled, batteries-included grids (AG Grid, MUI DataGrid, PrimeVue, Material): a complete grid with its own DOM, its own theming model, and its own (often paid) feature tiers. Fast to adopt, hard to re-skin to an arbitrary design system, and entirely framework-specific.

Rozie picks a third spot: it wires @tanstack/table-core, the same state machine the official adapters wrap, to the six reactivity systems once, by hand, inside DataTable.rozie, and emits a component per target. table-core owns no DOM (it is a pure createTable → setOptions → getRowModel pull-based machine), so the table markup is plain accessible HTML the framework owns, and Rozie's per-target emitter does the reactivity wiring that the adapters would otherwise each hand-write. The codegen enforces that no leaf ever imports a @tanstack/<fw>-table adapter: the single-core design is a build-time invariant.

Feature matrix ​

Cell legend: ✅ = documented out-of-the-box · ❌ = not supported / not present · ⚠️ = partial / consumer-assembly-required.

CapabilityReact (TanStack)Vue (TanStack / PrimeVue)Svelte (TanStack)Solid (TanStack)Angular (Material / AG)Lit (none)@rozie-ui/data-table
Headless row model✅✅✅✅⚠️❌✅ (table-core)
Sorting (multi-sort)✅✅✅✅✅❌✅ shift-click
Global + per-column filtering✅✅✅✅✅❌✅ filter-change
Pagination✅✅✅✅✅❌✅ page-change
Row selection✅✅✅✅✅❌✅ selectionMode
Column visibility / resize / pin✅✅✅✅✅ (AG)❌✅ three, with UI
Column reorder (drag a header)✅✅✅✅✅ (AG)❌⚠️ state only — columnOrder model + applyColumnOrder, no drag affordance ships
Sticky header✅✅⚠️✅✅❌✅ stickyHeader
Row virtualization (windowing)✅ (TanStack Virtual)✅ (TanStack Virtual)✅ (TanStack Virtual)✅ (TanStack Virtual)✅ (AG)❌✅ virtual (tested to 100,000 rows)
Expandable rows / master-detail✅✅✅✅✅ (AG)❌✅ expandable + #detail / getSubRows
Grouping + aggregation✅✅✅✅✅ (AG)❌✅ grouping + aggregationFn
Faceted filtering✅✅✅✅✅ (AG)❌✅ headless #filter + drop-ins
Inline editing (cell + full-row)⚠️ assemble⚠️ (PrimeVue ✅)⚠️ assemble⚠️ assemble✅ (AG)❌✅ four built-in editors + custom slot + validation
Cell range selection + clipboard (grid mode)❌❌❌❌✅ (AG)❌✅ Shift+Arrow / Shift+Click + Ctrl+C / Ctrl+X / Ctrl+V (range-tiling paste)
CSV / Excel file export❌⚠️ (PrimeVue exportCSV ✅)❌❌✅ (AG: CSV community, Excel enterprise)❌⚠️ clipboard TSV only — no file download
APG grid keyboard navigation (role="grid")❌⚠️ (PrimeVue)❌❌✅ (AG)❌✅ interactionMode="grid"
Declarative <Column> surface⚠️ defs array✅ (PrimeVue)⚠️⚠️⚠️—✅ <Column> + :columns
Custom cell / header rendering✅✅✅✅✅—✅ parent #cell / #colHeader, columnId-dispatched, plus an opt-in per-column cell-<columnId> / colHeader-<columnId> slot family (React/Solid render-prop, Lit property)
Server-side (manual) mode✅✅✅✅✅—✅ manual
No per-framework adapter❌❌❌❌❌—✅ single table-core
Idiomatic two-way state binding⚠️ state+onChange✅ v-model⚠️ stores⚠️ signal⚠️—✅ twelve r-model slices
Zero-config styling, re-skinnable⚠️ unstyled⚠️ themed⚠️⚠️styled-only—✅ 19 CSS-var tokens + shadcn/Material/Bootstrap bridges
Same API on all 6 frameworks❌❌❌❌❌❌✅

Where Rozie wins today ​

  • First-class packages everywhere, including Lit / web components, which have no headless data table to begin with. Each leaf is a real component for its framework, not a wrapper you assemble.
  • The same component surface everywhere. Where TanStack offers hooks (React), composables (Vue), stores (Svelte), and signals (Solid), four mental models over the same core, @rozie-ui/data-table is one <DataTable> + <Column> with the same props, twelve two-way slices, events, slots, and imperative handle: one grid to learn, document, and migrate across your stack.
  • No per-framework adapter. Every leaf wires @tanstack/table-core directly; the codegen forbids any @tanstack/<fw>-table import. You get the exact engine TanStack uses without the adapter-per-framework maintenance surface, and table-core is your peer dependency, so you control its version.
  • Twelve independent two-way state slices, controlled or uncontrolled. Data (so committed edits flow back), sorting, global filter, column filters, pagination, row selection, expansion, grouping, column visibility / sizing / order / pinning. Each is an optional r-model you bind only if you want to own it, and each change event fires regardless of binding so you can observe transitions either way.
  • Inline editing, cell + full-row. Mark a <Column editable> and bind r-model:data: the component owns the edit state and writes a fresh data array back on commit. Four built-in editor types (text / number / select / checkbox) plus editor="custom" for a headless #editor slot, synchronous per-column validation announced via aria-live, full-row edit (Shift+F2), and an opt-in per-column editor-<columnId> slot family with opt-in drop-in editor components, where TanStack ships only the row-model state and leaves the editors to you. A date editor ships as an opt-in drop-in component (EditorDate) used behind editor="custom", not as a built-in editor value — see Editing.
  • A declarative <Column> API (the PrimeVue-shaped surface) and a :columns config-array escape hatch, resolved by an id-keyed last-write-wins union, plus custom cell/header rendering via a single parent #cell / #colHeader scoped slot dispatched by columnId (a render-prop on React/Solid and a property on Lit, the one documented divergence) — or, per column, an opt-in cell-<columnId> / colHeader-<columnId> / filter-<columnId> / editor-<columnId> static named fill that wins over the shared dispatch-by-columnId slot for just that column. See Columns — per-column slot families for the precedence and gate rules.
  • Zero-config styling that re-skins to any design system. The table's primary surfaces are themed through 18 --rozie-data-table-* CSS custom properties, each with a built-in fallback, plus ready-made token bridges for shadcn/ui, Material 3, and Bootstrap 5 — with no required CSS import (on Lit, set them at or above the <rozie-data-table> element — :root, a wrapper, or the element itself — never on the .rozie-data-table classes, which render inside its shadow root). Some internal details (the grid active-cell ring, range highlight, fill handle, the per-header column ⋯ menu, and most of the pagination bar beyond its control border/radius/disabled-opacity) are not yet part of the public token surface; see Theming.
  • Opt-in WAI-ARIA grid mode. Set interactionMode="grid" for the full APG grid pattern: role="grid", a roving single tab-stop, and 2-D arrow-key cell navigation that survives re-sorts, filters, page changes, and column hide/reorder/pin. Cell ranges carry full spreadsheet clipboard semantics (Ctrl+C copy, Ctrl+X cut, Ctrl+V range-tiling paste with per-column coercion + validation), and the active cell is drivable and observable through dedicated handle verbs and events. The default interactionMode="table" stays a plain accessible table, byte-for-byte unchanged. See Grid mode for the full keyboard, clipboard, and active-cell contract.
  • Opt-in row and column windowing. Set virtual to true / 'rows' (vertical row windowing — unchanged from before), 'columns' (horizontal column windowing), or 'both' to window either or both axes inside a bounded scroll container, with correct aria-rowcount / aria-rowindex, measured variable-height rows, and sticky-header + pinned-column geometry preserved. Pinned, active, and editing columns/rows always stay rendered regardless of scroll position, headers and the filter row window with a clamped colspan, and absolute row/column indices stay addressable via focusCell / getActiveCell / activecell-change no matter what is currently rendered. The handle's scrollToRow(index, options?) scrolls a windowed table to any row index without moving grid-mode focus, and getScrollElement() returns the scroll container, so no consumer has to reach into internal class names. A separate autoMeasure flag makes the row-height estimate content-driven: instead of a fixed seed, the windowing engine feeds estimateSize() a running mean of measured row heights so the scrollbar/total height converges to the true content total on a large table with variable-height rows. It is built on the framework-agnostic @tanstack/virtual-core with no per-framework virtual adapter, and tested to 100,000 rows. The default virtual="false" is byte-identical to a non-virtual table, and virtual="true" / 'rows' is byte-behavior-identical to the row-only windowing this family has always shipped. See Virtualization for the windowing model, the table-layout: fixed consequence of column windowing, and the auto-measure behavior.
  • Opt-in grid-wide undo/redo. Set undoable (grid mode) and every committed data mutation (cell/row edit, paste, fill, cut, clear) becomes one undo step (Ctrl/Cmd+Z / Ctrl/Cmd+Y), replayed through the same writeData funnel every mutation already commits through. A bounded undoLimit caps memory, an external dataset swap clears history, and the undo / redo / canUndo / canRedo / clearHistory verbs plus the history-change event let you wire your own toolbar. No incumbent ships cross-framework undo/redo over a headless grid at all.
  • Virtual lazy loading for server-driven lists. Set virtual, manual, and rowCount to the server's total and pass data as a sparse array — an undefined/null entry (or anything past data.length) renders as a placeholder row (aria-busy, a #placeholder slot per cell, not selectable/expandable/editable/activatable) instead of a real data row. visible-range-change { start, end } reports the rendered window (overscan included) whenever it changes, so you fetch exactly what's on screen and write those rows in; a placeholder is replaced in place, so the scroll position doesn't move. Pair it with getRowId — table-core otherwise keys rowSelection / expanded by row position, so a row inserted above a selected one silently moves the selection onto the wrong row — so selection and expansion keep following the right row as data arrives or shifts. See Virtualization — lazy loading for the full contract.
  • A dedicated row-activate event. Click a body data row outside any control in the clicked cell (a selection checkbox, expander, link, input, drop-in, or an open editor), or, in grid mode, press Enter on a non-editable cell that has no controls of its own, and row-activate fires { row, index, trigger } — the original data object, its position in the rendered model, and 'click' or 'keyboard'. List-style "open this row" gestures (an email thread, an audit-log entry) get one event with the selection/editing edge cases already excluded, instead of re-deriving "was this a real row click" from the DOM yourself.

What Rozie defers ​

  • AG Grid's enterprise depth. AG Grid ships tree data, pivoting, integrated charting, and a deep server-side row model. @rozie-ui/data-table now covers row grouping + aggregation, expandable rows / master-detail, faceted filtering, inline editing, and cell range selection on top of the common surface (sort / filter / paginate / select / column management) plus a manual server-side hook, but not AG's full enterprise feature set.
  • CSV / Excel file export. There is no exportCsv() verb and no file download. Grid mode copies a selected range to the clipboard as TSV (Ctrl+C / Ctrl+X), which pastes directly into Excel, Sheets, or Numbers — so the common "get this into a spreadsheet" path is covered without a file. What is genuinely missing is downloading a .csv / .xlsx, including the whole table rather than a selection, and any control over the exported column set or formatting. AG Grid ships CSV export in its community build and Excel export in enterprise; PrimeVue's DataTable has a built-in exportCSV(). The headless TanStack families leave it to the consumer, as we currently do — but mind the sharp edge: you own the data array you passed in, and the handle exposes getSelectedRows() and getColumnDefs(), so exporting raw or selected rows is a short function. What the handle does not expose is the derived sorted-and-filtered row view, so "export exactly what I am looking at" currently means re-applying your own sort/filter over data rather than reading it back off the table.
  • @rozie-ui/data-table is pre-1.0 and younger and less battle-tested than the established libraries. The full prop / slice / event / handle surface is documented in the API reference.

Try it ​

The @rozie-ui/data-table overview & install documents the @rozie-ui/data-table-* packages — one pre-compiled, per-framework install (npm i @rozie-ui/data-table-react, etc.) — and links each concept page; the API reference carries the dense prop / slice / event / handle tables. The state engine is @tanstack/table-core (a peer dependency you control); there is no required CSS — a fully-tokenised skin ships inside the component, with optional one-line theme bridges for shadcn/ui, Material 3, and Bootstrap 5. The live demo runs the real Vue package in the page.

Cross-references ​

Pre-1.0 — APIs may change between minor versions.