TanStack/table
github.com/TanStack/table ↗ · 4000 files · 13.8 MB · scan capped
Become a Sponsor! TanStack Table [!NOTE] You may know TanStack Table by the adapter names: - Angular Table - Lit Table - Octane Table - Preact Table - React Table - Solid Table - Svelte Table - Vue Table - Alpine Table - Ember Table A headless table library fo
Overview
TanStack Table is a headless TypeScript datagrid library for developers who want to own their markup and styling while reusing table behavior across ten framework adapters. At commit 23f21c194e6eba2e7f0321e817104d6e118d6141, its v9 architecture combines explicitly registered features, replaceable row-model factories, and atom-based state ownership. The repository also contains framework examples, development inspection panels, and an experimental Web Worker pipeline; it is principally a collection of published libraries rather than one deployed application.
System map
The system centers on a framework-independent table engine, with adapters supplying rendering and reactivity integration. Table state resides in an atom graph, while row-model factories derive the data presented to renderers. Workers and devtools introduce separate execution and inspection boundaries without turning the core into a backend service. Distribution uses workspace packages with explicit ESM exports, and examples provide independently runnable application surfaces.
- The public product is a headless datagrid library with adapters for React, Preact, Octane, Vue, Solid, Svelte, Angular, Ember, Lit, and Alpine; application authors supply markup, components, and styles.
- The React entrypoint re-exports table-core and adds FlexRender, Subscribe, createTableHook, contexts, and useTable; the core entrypoint exposes constructors, types, helpers, features, and row-model factories.
- constructTable owns feature registration, resolved options, writable baseAtoms, readonly effective atoms, and a shallow-compared aggregate store; initial state determines the registered state keys.
- Row processing is a lazy factory pipeline: core → filtering → grouping → sorting → expansion → pagination. Each optional stage can return its predecessor when its factory is absent or its manual mode is enabled.
- The experimental worker integration has an application-created dedicated Worker and a shadow table inside that worker; registered processing stages share a handle and communicate through data/process/result messages.
- Shared devtools are implemented with Solid and lazily loaded through constructCoreClass; framework registration adapters connect tables to a key-based registry, while documented defaults disable live tools in production.
- The private root workspace pins pnpm@12.4.1 and requires Node >=20. Nx coordinates affected or all-package builds and checks, while pnpm-workspace.yaml links examples and library packages to local workspace versions.
- table-core publishes explicit subpath exports and builds unbundled ESM plus declarations before rewriting its declarations. Inspected Alpine/Lit examples use Vite on port 7777; the listed Octane examples additionally install the Octane compiler plugin.
Feature catalog
Features are developer-facing APIs that applications turn into controls and rendered grids. The stock registry includes table transformations, column management, row interaction, and spreadsheet-style cell behavior. Examined examples add virtualization, asynchronous query ownership, and worker processing around those APIs. This catalog describes the inspected feature families and examples; the supplied inventory and this investigation are bounded.
- Sorting supports click toggles, Shift-based multi-sort, removal, maximum multi-sort columns, custom comparator functions, named comparator registries, undefined placement, and inverted ordering.
- Column filtering, global filtering, and column faceting are independently exported capabilities; faceting supplies row models, unique-value counts, and min/max factories, while filtered processing is replaceable or bypassable.
- Grouping and aggregation are separate features. The worker example demonstrates custom grouping values, sum/mean aggregations, and multiple named aggregate results such as median plus extent rendered by aggregatedCell.
- The stock registry exposes column ordering, visibility, start/end pinning, sizing, and resizing, alongside row expansion, pagination, pinning, and selection. columnResizingFeature requires columnSizingFeature at the typed registration boundary.
- Cell selection provides selected IDs, range-value grids, counts, and data-change resets; cell spanning integrates with selection so a range clipping a merged cell expands to enclose it.
- React offers selector-based useTable subscriptions, table.Subscribe, bound FlexRender, and reusable createTableHook contexts/components. The spreadsheet example adds keyboard navigation, editing/undo commands, and both row and column virtualization through separate integrations.
- The Query example demonstrates offset and cursor pagination over simulated asynchronous data, with sorting/filtering performed outside the table. The worker example replaces filtering/grouping/sorting factories while retaining expansion, pagination, and selection on the main thread.
- Devtools let users select among registered tables and inspect features, state, options, rows, and columns. Registration requires a table key; adapters support conditional registration and explicit production entrypoints.
Data flow
Applications supply row data, column definitions, feature configuration, and optional external state owners. Construction turns configuration into effective state atoms and feature APIs; row-model reads then derive renderer-facing rows. Updates pass through owner-specific callbacks rather than a durable storage layer in the inspected core. Additional boundaries include worker message transfer, example query caches, and example-owned browser persistence.
- createCoreRowModel memoizes on options.data identity. accessRows assigns IDs through getRowId, constructs rows, populates flatRows and rowsById, and recursively reads children through the optional getSubRows callback.
- constructTable removes metadata slots, row-model factories, and function registries from actual feature modules, combines remaining modules with coreFeatures, and invokes their defaults and initialization hooks.
- Effective state reads prefer options.atoms[key], then an own property in options.state, then baseAtoms[key]. Explicitly undefined controlled slices fall back to initialState, and table.store aggregates effective slice values.
- table_setSorting calls setStateSlice, which resolves onSortingChange and evaluates its equality guard inside the owner's updater. The default makeStateUpdater writes to an external atom when supplied, otherwise to the internal base atom.
- React stages current options during render without synchronizing external state, selects a shallow-compared root snapshot, then marks that render committed and publishes its captured controlled state in an isomorphic layout effect.
- Worker synchronization sends changed data separately from requested stages and state. The shadow table batches received state into base atoms, computes requested models, serializes payloads with transferables, and returns requestId/dataVersion for acceptance checks.
- The Query example places pagination, sorting, and globalFilter in its query key, calls local fetchData, and passes returned rows/rowCount into a manually processed table. fetchData waits 500 ms, filters/sorts generated data, and slices the requested page.
- The shadcn-base example owns theme persistence: ThemeProvider reads vite-ui-theme from localStorage, validates light/dark/system, writes selections back, and applies the effective theme as a document-root class.
Behavior traces
The source walks cover startup, a sorting action, worker processing, asynchronous query pagination, and browser preference persistence. These traces name execution order and retain important bypass and fallback conditions. They describe code behavior without asserting that a browser session or test run reproduced it.
- Sorting-example startup proceeds document.getElementById('root') → ReactDOM.createRoot(...).render(<App />) → makeData(1_000) in useState → useTable with statically registered sorting features and columns; a missing root throws.
- useTable's initial useState constructs a table with reactReactivity, attaches Subscribe/FlexRender, and creates a render-phase source. constructTable resolves defaults, creates effective atoms/store, then runs each feature's initialization and API hook in one ordered pass.
- A sortable header click executes getToggleSortingHandler → column_toggleSorting → table_setSorting → setStateSlice → onSortingChange. Disabled columns ignore the event; multi-sort uses isMultiSortEvent and the toggle selects add/remove/toggle/replace behavior.
- After sorting state changes, createSortedRowModel reads sorting and the pre-sorted model, skips empty or unusable sort specifications, resolves comparators, sorts copied arrays recursively, and the example renders the first ten getRowModel rows through FlexRender.
- Worker-example startup executes the '/' route loader → getInitialData server function → makeData(10_000) → Route.useLoaderData → useTable. Its module-scoped handle creates a Worker lazily on client reads; without a Worker global, synchronization returns and row-model getters use pre-stage fallbacks.
- A worker-stage read executes syncTableWorker → ensureWorker → data post when identity changes → postProcess. initTableWorker receives data/state and serializes stage results; handleResult rejects stale requests/data, updates stage versions and worker state, then triggers a trailing request if more input arrived.
- Query-example sorting invokes its controlled onSortingChange, updates sorting and resets pageIndex to zero, changes the useQuery key, calls fetchData, and supplies the resulting page back to useTable; manual flags prevent duplicate client filtering/sorting/pagination.
- Theme persistence executes ModeToggle menu click → context setTheme → localStorage.setItem → React state update → ThemeProvider effect updating root classes. On remount, getItem and isTheme restore the choice; system mode resolves matchMedia when the effect runs.
Testing signals
Tests and CI were inspected, not executed; no passing result or coverage percentage is asserted. Examined suites span core semantics, React integration, worker protocol bookkeeping, declaration generation, and browser examples. Coverage configuration is report-only, and the inspected browser configuration targets Desktop Chrome. Inspection was selective, so coverage omissions below describe limits of particular checks rather than whole-repository absence.
- table-core uses Vitest with jsdom, globals, fixture setup, and V8 coverage of src/**. Its configuration explicitly enforces no coverage thresholds, so a generated report alone is not a coverage gate.
- Core implementation tests exercise prototype-named column IDs, null-prototype lookup maps, and merged-cell selection expansion, including checks that neighboring cells remain unselected.
- React tests use Testing Library and Vitest for adapter ownership/lifecycle, a regression involving unstable data and controlled expansion, and Node-environment server rendering of state, headers, cells, and footers.
- The inspected worker unit harness replaces Worker with FakeWorker and asserts data-identity messaging, single-flight behavior, trailing stage requests, and rebuilt row order; these assertions do not themselves exercise real worker bundling or structured-clone transport.
- Playwright selects tests through PLAYWRIGHT_TEST_DIR, uses one Desktop Chrome worker per example process, and retains failure traces/screenshots. The sorting smoke suite checks initial rows/state, data regeneration, and inferred sort-direction labels.
- Core type checks run tsc plus a dedicated declaration-emission project, with a fixture exporting inferred sharedTableFeatures. Package build validation also uses publint --strict, separate from runtime assertions.
- PR CI runs affected lint, dependency hygiene, library, type, build-validation, and build targets, and collects core coverage separately. Release CI runs all configured checks before Changesets, but its browser audit depends on the release job and excludes rc.
- Separate workflows validate skill content/snippets, apply formatting, and analyze GitHub Actions with zizmor. Their definitions were inspected only; none establishes an executed result, and several package test scripts explicitly permit no tests.
Risk map
The strongest observed risks concern worker lifecycle and correctness, state publication, and example/tooling execution boundaries. These are source observations, not confirmed vulnerabilities or reproduced failures. Inspected core flows operate on application-provided data and callbacks; this investigation does not establish repository-wide authentication or network-service properties. CI definitions reveal a material ordering distinction between package release and browser auditing.
- The worker handle documents that automatic termination is not wired and that StrictMode double mounts/HMR can leak development workers. terminate resets tracked bridges explicitly, but the inspected React useTable implementation does not call it.
- Worker processing requires matching thread-portable configuration and an offloaded upstream prefix. A missing upstream worker result can bypass main-thread processing; mismatch warnings are restricted to development.
- Worker errors mark the bridge failed, terminate its Worker, clear pending state, and stop subsequent synchronization while retaining last results. This creates an explicit stale-output mode requiring application attention rather than an automatic synchronous recomputation.
- State publication and reference equality are high-impact coupling points: controlled expansion plus fresh data arrays previously sustained a render loop according to the regression harness. setStateSlice's owner-side guard and React's captured commit snapshot are deliberate protections to preserve.
- User-provided row/column IDs reach caches and lookup dictionaries, including names such as __proto__. makeObjectMap and cloneState use defensive construction, and dedicated tests assert prototype-key behavior; these protections are a review boundary, not proof of universal input safety.
- Example theme persistence directly accesses localStorage/window without exception handling in ThemeProvider. The initializer validates stored strings, but browser storage availability and access failures are not handled in this inspected path.
- Inspected example Vite configs allow all hosts, while E2E infrastructure reads example manifests/configs and may spawn pnpm exec vite with inherited environment and process-group cleanup. These are development execution boundaries; the programmatic test server explicitly binds loopback.
- Release grants contents/id-token/pull-request write permissions and invokes Changesets before the dependent E2E audit. Browser failures can therefore be discovered after publication; the inspected PR test command has no explicit E2E step.
Extension points
Customization is explicit and predominantly configured per table. The features object combines runtime modules, row-model factories, function registries, and type-only metadata while retaining their distinct roles. Framework adapters provide a reactivity contract, and React's hook factory binds application components and contexts. Worker processing and devtools expose additional public integration seams.
- tableFeatures is a runtime identity helper with compile-time ValidateFeatureSlots constraints. FeatureSlotPrereqs validates pairings such as resizing/sizing, global/column filtering, and sorting factories/rowSortingFeature.
- Custom modules can declaration-merge Plugins and feature-map interfaces; the worker plugin demonstrates registering both workerRowModelsFeature and its state slice without application-side module declarations.
- Feature hooks separate shared prototype APIs, table APIs, defaults, per-instance initialization, and reset behavior. Runtime construction invokes each feature's table initialization immediately before its API construction in registration order.
- Row-model slots accept factory replacements; core falls back to createCoreRowModel, while optional derived stages fall back to predecessors. createWorkerRowModel occupies those same slots rather than introducing a separate renderer API.
- sortFns, filterFns, and aggregationFns are per-table registries whose keys participate in inference. Sorting also accepts comparator functions directly, resolves auto-selected names, and falls back to basic when a named function is unavailable.
- createColumnHelper supports key/function accessors, display columns, nested groups, and tuple-preserving column arrays. metaHelper supplies phantom tableMeta/columnMeta types, which construction removes from the runtime feature list.
- TableReactivityBindings is the adapter seam for writable/readonly atoms, subscriptions, batching, untracking, scheduling, options stores, and optional commit/unmount operations; React inserts its binding before constructing the core table.
- createTableHook binds table/cell/header component collections and contexts into useAppTable, while devtools registration managers expose update/dispose and key-based leases for integrating additional adapter lifecycles.
Contributor handoff
Begin with contribution rules, then follow the affected feature from example controls into core utilities and adapter publication. Core changes have cross-framework reach, so distinguish algorithm behavior from framework scheduling and rendering contracts. Tests and CI were inspected, not executed in this audit; future execution requires a separately authorized environment. The inventory and investigation are bounded, and source comments or configured checks must not be reported as runtime verification.
- Read CONTRIBUTING.md before analysis or changes, keep a PR focused on one topic, understand AI-generated changes, preserve the required PR sections, and add a changeset for published-package changes.
- Use the root pnpm workspace and existing Nx scripts. build targets affected packages; build:all targets all non-example packages; package-level test:lib, test:types, and test:build provide narrower starting points for authorized verification.
- For code contributions, CONTRIBUTING.md requires both pnpm test and pnpm test:e2e before opening a PR, with browser installation potentially needed. This audit inspected those commands and CI definitions without executing them.
- Preserve documented dependency exceptions: TypeScript 6.0.3, Ember Babel 7.29.7, Svelte dnd-kit 0.2.x, Vitest 4, Octane 0.1.36, specified Pacer devtools versions, and Preact Form 1.30.5 require separate migrations.
- Before changing state ownership, read constructTable, coreTablesFeature.utils, and setStateSlice. External atoms outrank controlled state, equality runs against the owner's value, and option merging preserves initial features, atoms, and initialState.
- For React changes, preserve render-versus-commit publication and stable App wrapper component identities. useAppTable keeps current tables in a ref because useTable returns a changing object; recreating wrappers would remount subtrees and disrupt focused inputs.
- For worker changes, inspect both bridge and shadow-table code plus shared example configuration. Preserve dataVersion/request checks, coalescing, consistent processing functions, pre-result fallbacks, and explicit termination semantics.
- Keep scope-specific verification distinct: adapter tests target lifecycle/state integration, worker unit tests use a fake transport, Playwright targets Chrome, and coverage has no thresholds. None of these inspected definitions establishes full adapter/browser coverage or a passing result.
Languages
| Language | Files | Share of source |
|---|---|---|
| TypeScript | 3125 | 80% |
| Markdown | 1398 | docs/config |
| JSON | 1311 | docs/config |
| HTML | 447 | docs/config |
| CSS | 406 | docs/config |
| JavaScript | 316 | 8% |
| Svelte | 288 | 7% |
| Vue | 185 | 5% |
Start reading here
Probable source entrypoint: packages/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
packages/ (691 files)
Repository structure
| Top-level path | Files |
|---|---|
examples/ | 1991 |
docs/ | 1262 |
packages/ | 691 |
scripts/ | 12 |
.github/ | 10 |
_artifacts/ | 3 |
tests/ | 2 |
.changeset/ | 1 |
Docs found: AGENTS.mdCONTRIBUTING.mdREADME.mddocs/agent-skills.mddocs/devtools.mddocs/framework/alpine/guide/aggregation.mddocs/framework/alpine/guide/cell-selection.mddocs/framework/alpine/guide/cell-spanning.mddocs/framework/alpine/guide/column-faceting.mddocs/framework/alpine/guide/column-filtering.md
Package
package.json — table, 29 declared dependencies.
@changesets/changelog-github@changesets/cli@faker-js/faker@playwright/test@size-limit/preset-small-lib@tanstack/eslint-config@tanstack/intent@tanstack/typedoc-config@testing-library/jest-dom@types/node@typescript-eslint/parser@vitest/coverage-v8
CI & config entrypoints
README.md— Repository readmeAGENTS.md— Agent instructions.github/workflows/autofix.yml— GitHub Actions workflow.github/workflows/check-skills.yml— GitHub Actions workflow.github/workflows/pr.yml— GitHub Actions workflow.github/workflows/release.yml— GitHub Actions workflow.github/workflows/zizmor.yml— GitHub Actions workflowexamples/alpine/realtime-trading/vite.config.ts— Vite configexamples/lit/realtime-trading/vite.config.ts— Vite configexamples/octane/aggregation/vite.config.ts— Vite config
Structural health signals
Average score 6.4/10 across 96 analyzed source files; 58 hotspots surfaced.
examples/react/kitchen-sink/src/routes/index.tsx— 1151 lines, score 5 (Large file, Deep nesting)examples/lit/kitchen-sink/src/main.ts— 987 lines, score 5 (Large file, Deep nesting)examples/react/kitchen-sink-shadcn-base/src/components/data-table/data-table-filter-list.tsx— 922 lines, score 5 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
23f21c194e6e2026-10-01 — chore: refresh Start example dependencies (#6605)21d713fc49472026-09-16 — chore: stabilize example E2E runs in CI (#6593)affb9e1d601a2026-09-15 — docs: disable generated page titles (#6588)0bdf588f98e92026-09-14 — chore: package upgrades (#6592)ce123bc6651b2026-09-10 — chore: remove e2e parellel for realtime trading