Skip to content

Compared to Other Grids

This page compares @toolbox-web/grid against three widely-used JavaScript data grids: AG Grid, Tabulator, and SlickGrid. The goal is an honest side-by-side on features, bundle size, and — below — measured performance in your own browser.

They bracket the market. AG Grid sets the upper bound on feature breadth and bundle size, Tabulator is the mainstream MIT option, and SlickGrid (the maintained 6pac fork) the minimalist, virtualization-first one. Framework-locked grids — MUI DataGrid, PrimeNG Table, Vuetify DataTable — are out of scope: @toolbox-web/grid is framework-agnostic, and comparing it only against those would skew the picture.

Sources and disclaimer

Feature availability comes from each vendor’s public documentation — AG Grid, Tabulator, and SlickGrid (6pac fork v2.x). Handsontable and TreeGrid are not in the feature table; they appear only in the pricing table and the alternatives named further down. All figures last verified 9 September 2026.

AG Grid is a trademark of AG Grid Ltd; Handsontable is a trademark of Handsoncode; TreeGrid and FastGrid are products of Coqsoft. This is an independent comparison, not affiliated with, endorsed by, or sponsored by any listed project. Verify current features and pricing on each project’s own site.

Aspect@toolbox-web/gridAG GridTabulatorSlickGrid
LicenseMITMIT (Community) / Commercial (Enterprise)MITMIT (6pac fork)
CostFreeFree / $999 per developer (Enterprise)FreeFree
Bundle Size (gzipped)~47 KB~200 KB+ (Community) · ~300 KB+ (Enterprise)~90 KB~30 KB + jQuery
DependenciesZeroZeroZerojQuery
Framework SupportAll (Web Component)All (wrappers)All (wrappers)Vanilla / jQuery
TypeScript✅ Bundled✅ Bundled✅ BundledCommunity types

Legend: ✅ first-party support (built-in or official plugin/module) · ⚙️ requires third-party extension or manual wiring · ❌ not available · For AG Grid, values are shown as Community / Enterprise when they differ.

Feature@toolbox-web/gridAG Grid (CE / Ent)TabulatorSlickGrid
Row Virtualization✅✅✅✅
Column Virtualization✅✅✅✅
Sorting (Single & Multi)✅✅✅✅
Column Resizing✅✅✅✅
Column Reordering✅✅✅✅
Column Pinning/Freezing✅✅✅✅
Row Pinning✅✅✅✅
Cell Selection✅✅✅✅
Row Selection✅✅✅✅
Checkbox Selection✅✅✅✅
Custom Cell Renderers✅✅✅✅
Cell Editing✅✅✅✅
Custom Editors✅✅✅✅
Keyboard Navigation✅✅✅✅
Themes/Styling✅ (6 built-in)✅✅ (5 built-in)Minimal
CSV Export✅✅✅✅
Filtering✅✅✅✅
Set / Multi-value Filters✅❌ / ✅ Paid✅⚙️
Pagination⚙️ (virtualized by default — no pager UI)✅✅⚙️
Column Visibility✅✅✅✅
Column & Filter Tool Panels✅❌ / ✅ Paid⚙️⚙️
Row Animations✅✅Limited❌
Range Selection✅❌ / ✅ Paid✅⚙️
Clipboard (Copy/Paste)✅❌ / ✅ Paid✅⚙️
Excel Export✅❌ / ✅ Paid✅ (basic)⚙️
Row Grouping✅❌ / ✅ Paid✅✅
Aggregation/Footers✅❌ / ✅ Paid✅✅
Pivot Table✅❌ / ✅ Paid❌❌
Tree Data✅❌ / ✅ Paid✅✅
Master-Detail✅❌ / ✅ Paid✅⚙️
Server-Side Row Model✅❌ / ✅ Paid✅ (progressive)Manual
Infinite Scrolling✅✅✅Manual
Context Menu✅❌ / ✅ Paid⚙️⚙️
Column Groups (spanning headers)✅✅✅✅
Cell Spanning (row/col span in the body)❌✅❌⚙️ (getItemMetadata)
Row Drag & Drop✅✅✅⚙️
Print✅❌ / ✅ Paid✅❌
Undo/Redo✅✅✅ (history)❌
Responsive/Mobile Layout✅ (card layout)Manual✅ (collapse)Manual
Framework Agnostic✅ Native Web ComponentWrapper-basedWrapper-basedjQuery-based
Tree-Shakeable Plugins✅PartialPartial (modules)❌
Styled Excel Export (fonts, fills, number formats, merges)✅❌ / ✅ Paid❌❌
Integrated Charts⚙️ (bring your own chart library)❌ / ✅ Paid❌❌
Built-in Sparklines⚙️ (recipe)❌ / ✅ Paid❌❌
End-user Cell Formulas⚙️ (recipe)❌ / ✅ Paid❌❌
Dedicated Support with SLAs❌❌ / ✅ Paid❌❌

Pick a competitor from the dropdown, then click Run Comparison. It iterates through increasing data sizes (5K → 1M rows), running Toolbox Grid and the chosen competitor sequentially in your browser with equivalent data and configuration. Results are cached per competitor for the lifetime of the page, so you can switch back without re-running.

Ready
Show Grid Configurations

Toolbox Grid

// Configuration displayed here

Competitor

// Configuration displayed here

Grid will render here during benchmarks (one at a time)

Pick a competitor and click Run Comparison to benchmark both grids at increasing data sizes.

Scale points: 5K → 100K → 500K → 1M rows × 10 columns. Macro ops: cold mount to painted viewport, full-dataset sort and filter, and replacement to painted viewport — these drive the headline speedup. Round-trip sums the macro times. Micro ops (within-frame interactions): single-row update, column resize, scroll-to-end, destroy — reported separately because most ties cluster at the ~16 ms frame floor on every virtualized grid. Viewport overhead: DOM node count after first paint. Every sample is accepted only after an untimed outcome validation; each timed metric runs 5× per scale point (trimmed mean).

How this benchmark works
  1. The selected competitor and its CSS theme are loaded once from a CDN. The exact version is shown; SlickGrid is pinned to its last global-script release.
  2. Cold mount starts from an empty host for both grids and is measured through the browser’s next paint opportunity.
  3. Each timed metric runs five times and reports a trimmed mean. Sort inputs use identical seeded permutations.
  4. Execution order alternates at each scale point to reduce systematic warmup, thermal, and garbage-collection bias. Cooldowns reduce immediate contention but do not guarantee garbage collection.
  5. Every sample has an untimed semantic postcondition. The run aborts instead of publishing a number when mounting, sorting, filtering, replacement, update, resize, navigation, or teardown does not produce the required outcome.
  6. Results build up progressively, showing the scaling curve. Switching the dropdown shows cached results instantly; you only re-run when you click the button again.

Expand Show Grid Configurations in the benchmark to inspect the settings. Each competitor has its own adapter under apps/docs/src/components/demos/competitors/; adapters use the closest public API for the same user-visible outcome. Browser results remain indicative rather than controlled laboratory measurements.

The headline compares only operations with a meaningful public-API equivalent across all included grids:

OperationTimed intervalRequired postcondition
Cold mountEmpty host to painted viewportFull source count is modeled and at least one row is painted
SortRequest descending numeric sort to painted viewportFirst and last model rows have the expected IDs
FilterRequest id > rowCount / 2 to painted viewportActive count is exactly half and surviving IDs satisfy the predicate
ReplaceAssign marked replacement data to painted viewportFull count is modeled and the marker is visible
UpdateKeyed row patch to painted viewportThe addressed model row contains the new value
ResizeSet one column width to painted viewportThe public column state reports the requested width
NavigateRequest the final row to painted viewportThe scrolling viewport reaches its end
DestroyPublic teardown plus host removal to painted viewportThe benchmark host is disconnected

DOM-node count is a separate viewport-overhead statistic, not a memory measurement. Frame-bound update, resize, navigation, and destroy timings are reported separately from the macro headline. Grouping, pivoting, editing, tree data, export, and multi-column sorting are excluded — they are not universal enough across the four grids to score fairly, and showing an unsupported capability as a zero-time win would be dishonest.

Only free grids are in the chart. AG Grid Enterprise, Handsontable, and TreeGrid all need a licence key, and re-hosting a build so your browser could run it would break the terms of the products we’d be comparing against. AG Grid Community is included, and Enterprise is modules layered on the same rendering core.

SlickGrid is pinned to v2.4.x (the last release with the global Slick namespace) and pulls jQuery from CDN. Column reorder and drag are disabled to keep the dependency surface minimal.

@toolbox-web/grid is a native Web Component, not a wrapper around a framework-specific core. The same element and the same API work in vanilla JS/TypeScript, React, Angular, Vue, and Svelte. The optional adapters exist so you can write renderers and editors in your framework’s own idiom — JSX, Angular templates, Vue slots — not to make the grid work at all.

Most per-developer grid licences are justified by the same handful of features: row grouping with aggregation, pivot, tree data, master-detail, a server-side row model, range selection and clipboard, context menus, tool panels, styled Excel export. Those are the rows marked ❌ / ✅ Paid in the feature table above, and all of them are MIT here. No licence key, no seat count. Two rows run the other way — cell spanning and an SLA — and both are covered under When not to choose Toolbox Grid.

Handsontable and TreeGrid split things differently. Both are spreadsheet products first, so a row-for-row comparison doesn’t really hold up — check Handsontable’s feature list and TreeGrid’s yourself rather than trusting our summary of them. Short version: if your users type formulas, you want one of those, not this.

ProductLicencePublished list priceSource
Toolbox GridMIT$0 — no tiers, no seats, no licence keyLICENSE
AG Grid CommunityMIT$0ag-grid.com/license-pricing
AG Grid EnterpriseCommercial$999 per developer (perpetual, incl. 1 year of updates & support)ag-grid.com/license-pricing
AG Grid + AG Charts Enterprise BundleCommercial$1,498 per developerag-grid.com/license-pricing
HandsontableCommercialfrom $999 per developer / yearhandsontable.com/pricing
TreeGrid StandardCommercial (perpetual)from $1,300 for the first developer, plus per-developer additions and 30 %/year maintenancetreegrid.com/Prices

Prices verified 9 September 2026. List prices in USD, excluding tax, volume discounts, OEM/SaaS redistribution terms, and negotiated enterprise agreements. Confirm on the vendor’s own pricing page before budgeting.

The recurring cost usually matters more than the first invoice. Ten developers on an Enterprise tier is a five-figure yearly line item that grows with the team, plus a licence key to renew and keep out of your public bundle.

Here’s what we don’t do. Where there’s a documented way to get there yourself, it’s linked. Where there isn’t, there’s the name of something that does it properly.

Most of these come down to the same trade-off. The grid has a hard bundle budget and no opinion about which libraries you use. A renderer returns a DOM node and a valueAccessor returns a value, so the charting library, the formula engine and the PDF writer are your call rather than ours. You get to pick, but you also have to wire it up. Plenty of teams would rather it came in the box.

Spreadsheet workloads where users write the formulas

Section titled “Spreadsheet workloads where users write the formulas”

There’s no formula engine and we’re not adding one — HyperFormula on its own is roughly four times the size of the whole grid. valueAccessor covers developer-defined computed columns, and those sort, filter, aggregate and export like any other column. The spreadsheet formulas recipe goes further and wires HyperFormula in behind it, including letting users type =SUM(...) into an editable column. It works; it’s just not something we ship and support. If the product you’re building is a spreadsheet, buy a spreadsheet: Handsontable or FastSheet.

We don’t ship a chart library, and we won’t. AG Grid bundles AG Charts because they own both; we’d be picking a winner on your behalf and paying for it in the bundle.

The chart-from-grid workflow itself is buildable on public API. selectionPlugin.getSelection().ranges gives you the selected CellRange[], contextMenu.items takes a function so you can add a “Chart selection” entry, and shell.registerToolPanel({ render }) hosts the chart — render returns an optional cleanup function, so Highcharts, ECharts or Chart.js can be destroyed properly when the panel closes. selection-change and cell-commit keep it in sync. For charts inside cells, the sparklines recipe covers inline SVG and uPlot.

What you’re doing yourself is the wiring, the chart-type picker, and the settings UI. There’s no recipe for the full workflow yet. Gantt is a flat no — TreeGrid GanttChart does scheduling, and AG Grid Enterprise Bundle has AG Charts built in if you’d rather it came assembled.

Not supported. Columns can be grouped under a spanning header, and those spans carry through to the Excel export as merged cells, but a body cell can’t span rows or columns. Row spanning in particular fights the recycled-row virtualization the numbers above depend on, so it’s unlikely to change. AG Grid has it in Community, Handsontable has mergeCells, and SlickGrid does it through getItemMetadata.

The grid renders. It owns the viewport, the row recycler, the row and cell elements and their ARIA roles. You control what goes inside them through renderer, editor, slots and CSS custom properties, but not the elements themselves. If you need to own every node — a non-tabular layout, server-rendered rows, a design system that has to emit its own markup — use something headless. TanStack Table is the obvious one.

No pager component, no page-size dropdown, no paginationPageSize. The grid virtualizes, so showing everything is the normal case even at a million rows. ServerSidePlugin fetches in blocks and you can drive it from a pager you build, but you’re building and styling that yourself. If page numbers are a requirement, AG Grid and Tabulator ship one.

If the app already runs on jQuery and you want the smallest possible footprint, SlickGrid (6pac fork) is a good answer. A web component works there too, but you’d be adding a second paradigm instead of removing one.

No support contract, no SLA, no response-time guarantee. Issues and discussions on GitHub, answered when maintainers get to them. For some organisations that’s a procurement blocker regardless of the feature list. AG Grid Enterprise, Handsontable and TreeGrid all sell support.

Most concepts in the other grids map cleanly to Toolbox Grid. Pick the section for your current grid below.

AG GridToolbox Grid
columnDefscolumns or gridConfig.columns
rowDatarows
defaultColDefPer-type defaults via typeDefaults (keyed by column.type)
getRowIdgetRowId: (row) => string (same shape)
onCellValueChangedcell-commit event
onSelectionChangedselection-change event
serverSideRowModelServerSidePlugin
rowGroupingGroupingRowsPlugin
masterDetailMasterDetailPlugin

Side-by-side AG Grid → Toolbox Grid for the same minimal setup: data, columns, and row selection.

<!-- AG Grid (vanilla) -->
<div id="myGrid" class="ag-theme-quartz" style="height: 400px;"></div>
<script type="module">
import { createGrid } from 'ag-grid-community';
createGrid(document.querySelector('#myGrid'), {
rowData: rows,
columnDefs: columns,
rowSelection: 'multiple',
});
</script>
<!-- Toolbox Grid (vanilla) -->
<tbw-grid style="height: 400px;"></tbw-grid>
<script type="module">
import '@toolbox-web/grid';
import '@toolbox-web/grid/features/selection';
import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid('tbw-grid');
grid.gridConfig = {
columns,
features: { selection: 'row' },
};
grid.rows = rows;
</script>
TabulatorToolbox Grid
new Tabulator(el, options)<tbw-grid> element + grid.gridConfig = { ... }
columns: [{ field, title, ... }]columns: [{ field, header, ... }] (rename title → header)
data option / setData(rows)grid.rows = rows
index: 'id'getRowId: (row) => String(row.id)
layout: 'fitColumns' / 'fitDataFill'fitMode: 'stretch' / 'fixed'
setSort([{ column, dir }])grid.sort(field, 'asc' | 'desc')
setFilter(field, type, value)FilteringPlugin + filterPlugin.setFilterModel([...])
cellEdited callbackcell-commit event
rowSelected callbackselection-change event
dataTree: trueTreePlugin
pagination: 'local'No pagination plugin — the grid virtualizes all rows instead
<!-- Tabulator -->
<div id="myGrid" style="height: 400px;"></div>
<script type="module">
import { Tabulator } from 'tabulator-tables';
new Tabulator('#myGrid', {
data: rows,
columns: [
{ title: 'Name', field: 'name', headerSort: true },
{ title: 'Email', field: 'email' },
],
selectableRows: true,
});
</script>
<!-- Toolbox Grid -->
<tbw-grid style="height: 400px;"></tbw-grid>
<script type="module">
import '@toolbox-web/grid';
import '@toolbox-web/grid/features/selection';
import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid('tbw-grid');
grid.gridConfig = {
columns: [
{ field: 'name', header: 'Name', sortable: true },
{ field: 'email', header: 'Email' },
],
features: { selection: 'row' },
};
grid.rows = rows;
</script>

SlickGrid’s API is lower-level than the others — most behavior is composed from Slick.Grid + Slick.Data.DataView + plugin instances. Toolbox Grid bundles the equivalents into config and built-in plugins.

SlickGridToolbox Grid
new Slick.Grid(el, dataView, columns, options)<tbw-grid> + grid.gridConfig = { columns, ... }
Slick.Data.DataViewBuilt into the grid; pass plain arrays to grid.rows
columns: [{ id, name, field }]columns: [{ field, header }] (drop separate id; field is the key)
dataView.setItems(rows, 'id')grid.rows = rows + getRowId: (row) => String(row.id)
dataView.sort(comparer, asc)grid.sort(field, 'asc' | 'desc')
dataView.setFilter(fn)FilteringPlugin (declarative filter model)
Slick.Plugins.RowSelectionModelfeatures: { selection: 'row' }
Slick.CheckboxSelectColumnfeatures: { selection: 'checkbox' }
Slick.CellRangeSelector + CellSelectionModelRangeSelectionPlugin
onCellChangecell-commit event
onSelectedRowsChangedselection-change event
Manual jQuery resize handlerBuilt-in column resize feature
<!-- SlickGrid -->
<div id="myGrid" style="height: 400px;"></div>
<script type="module">
// SlickGrid 6pac fork v2.4.x — global Slick namespace
const dataView = new Slick.Data.DataView();
dataView.setItems(rows, 'id');
const grid = new Slick.Grid('#myGrid', dataView, [
{ id: 'name', name: 'Name', field: 'name', sortable: true },
{ id: 'email', name: 'Email', field: 'email' },
], { enableCellNavigation: true });
grid.setSelectionModel(new Slick.RowSelectionModel());
</script>
<!-- Toolbox Grid -->
<tbw-grid style="height: 400px;"></tbw-grid>
<script type="module">
import '@toolbox-web/grid';
import '@toolbox-web/grid/features/selection';
import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid('tbw-grid');
grid.gridConfig = {
columns: [
{ field: 'name', header: 'Name', sortable: true },
{ field: 'email', header: 'Email' },
],
getRowId: (row) => String(row.id),
features: { selection: 'row' },
};
grid.rows = rows;
</script>
  • Getting Started — Install and set up your first grid in minutes
  • Demos — Full-featured employee management demo
  • Plugins Overview — Complete plugin catalog with feature comparison
  • Support & Stability — Supported framework versions, browser baseline, deprecation window, and release cadence