Use this as the complete breaking-change checklist. V9 is the current API. The central Lit change is a stable controller constructed with the host, while current options are passed to .table(...) during render.
Framework prerequisites: Lit 3.1.3 or newer within v3 (lit ^3.1.3) and
@lit/context ^1.1.0.
Recommended Migration Order
- Replace the v8 controller options thunk with a host-only typed
TableController. - Pass current options to
controller.table(options, selector?)in render. - Move features, row models, and registries into
tableFeatures. - Update selected state, controlled ownership, and rendering.
- Apply every shared API and type rename below.
- Treat
stockFeaturesas a temporary audit bridge; explicit features are the production target.
const features = tableFeatures({
rowSortingFeature,
sortedRowModel: createSortedRowModel(),
sortFns: { alphanumeric: sortFn_alphanumeric },
})
class PeopleTable extends LitElement {
private controller = new TableController<typeof features, Person>(this)
protected render() {
const table = this.controller.table({ features, columns, data: this.data })
return html`<span>${table.getRowModel().rows.length} rows</span>`
}
}
Construction and Feature Registration
| v8 | v9 |
|---|---|
new TableController(this, () => options) |
new TableController<typeof features, TData>(this) |
controller.table property |
controller.table(options, selector?) call in render |
| All features bundled | Required features: tableFeatures({...}) |
getCoreRowModel() option |
Remove; core row model is automatic |
get*RowModel() table options |
create*RowModel() slots in tableFeatures |
sortingFns table option |
sortFns feature slot |
Top-level onStateChange |
Per-slice callbacks, external atoms, or store subscription |
Feature imports are cellSelectionFeature, columnFilteringFeature, globalFilteringFeature, rowSortingFeature, rowPaginationFeature, rowSelectionFeature, rowExpandingFeature, rowPinningFeature, columnPinningFeature, columnVisibilityFeature, columnOrderingFeature, columnSizingFeature, columnResizingFeature, rowAggregationFeature, columnGroupingFeature, and columnFacetingFeature. APIs are feature-gated. Put a feature before its dependent slot in one tableFeatures call. Aggregation is independent from grouping: register rowAggregationFeature for aggregation APIs and add columnGroupingFeature only for grouped rows.
Row-model mapping
| v8 option | v9 slot and factory |
|---|---|
getFilteredRowModel() |
filteredRowModel: createFilteredRowModel() after column filtering |
getSortedRowModel() |
sortedRowModel: createSortedRowModel() after row sorting |
getPaginationRowModel() |
paginatedRowModel: createPaginatedRowModel() after pagination |
getExpandedRowModel() |
expandedRowModel: createExpandedRowModel() after expanding |
getGroupedRowModel() |
groupedRowModel: createGroupedRowModel() after grouping |
getFacetedRowModel() |
facetedRowModel: createFacetedRowModel() after faceting |
getFacetedMinMaxValues() |
facetedMinMaxValues: createFacetedMinMaxValues() |
getFacetedUniqueValues() |
facetedUniqueValues: createFacetedUniqueValues() |
Factories take no arguments. Register filterFns, sortFns, and aggregationFns as sibling feature slots holding individually imported built-ins (filterFn_includesString, sortFn_alphanumeric, aggregationFn_sum) under their conventional keys. The full registry objects still work but bundle every built-in.
Lit State Migration
table.getState().sortingbecomestable.state.sorting,table.store.state.sorting, or narrowtable.atoms.sorting.get().table.statecontains all registered slices by default. Pass a second-argument selector tocontroller.table(...)only to narrow the render-selected surface.- Use
table.subscribe(table.store, stableSelector, renderCallback)for selected template state. Keep the selector reference stable outside render. - Controlled state uses Lit
@state()fields and matchingon[State]Changecallbacks that resolve value-or-function updaters. - Top-level
onStateChangeis removed. Use per-slice callbacks, external atoms, ortable.store.subscribefor all changes. - External atoms come from
@tanstack/storeand are provided throughatoms. Never provide bothatoms.paginationandstate.pagination. - The controller requests host updates for table and option-store changes; do not create a new controller in render.
- Treat
table.baseAtomsas internal writable state; prefer feature APIs or external atoms.
Rendering and Composition
| v8 | v9 |
|---|---|
flexRender(def, context) |
FlexRender({ cell }), FlexRender({ header }), or FlexRender({ footer }) |
| Standalone helper only | table.FlexRender({ cell }) is also available |
| Repeated raw options | tableOptions(...) composition |
| Repeated conventions | createTableHook({ features, ... }) |
createTableHook returns a host-bound app table helper and pre-bound column helper. Construct the app helper with the Lit host, then call its .table() during render. It is optional and intended for recurring application conventions.
Complete Shared Breaking-Change Map
Instance methods
Row, cell, column, header, and related methods now live on shared prototypes and use this. Call them on their instances. Do not destructure/pass them bare or expect them in object spread, Object.keys, or JSON. Table methods are not affected.
Logical column pinning
V9 has no left/right aliases.
| old | new |
|---|---|
columnPinning.left / .right |
.start / .end |
column.pin('left' | 'right') |
column.pin('start' | 'end') |
getIsPinned() === 'left' | 'right' |
'start' | 'end' |
row.getLeftVisibleCells() / getRightVisibleCells() |
getStartVisibleCells() / getEndVisibleCells() |
getLeftHeaderGroups() / getRightHeaderGroups() |
getStartHeaderGroups() / getEndHeaderGroups() |
getLeftFooterGroups() / getRightFooterGroups() |
getStartFooterGroups() / getEndFooterGroups() |
getLeftFlatHeaders() / getRightFlatHeaders() |
getStartFlatHeaders() / getEndFlatHeaders() |
getLeftLeafHeaders() / getRightLeafHeaders() |
getStartLeafHeaders() / getEndLeafHeaders() |
getLeftLeafColumns() / getRightLeafColumns() |
getStartLeafColumns() / getEndLeafColumns() |
getLeftVisibleLeafColumns() / getRightVisibleLeafColumns() |
getStartVisibleLeafColumns() / getEndVisibleLeafColumns() |
getLeftTotalSize() / getRightTotalSize() |
getStartTotalSize() / getEndTotalSize() |
column.getStart('left') |
column.getStart('start') |
column.getAfter('right') |
column.getAfter('end') |
column.getIndex('left' | 'right') |
column.getIndex('start' | 'end') |
Prefer CSS logical inset properties; logical names do not set DOM direction. columnResizeDirection is unchanged.
Pinning, sizing, and resizing
enablePinningsplits intoenableColumnPinningandenableRowPinning.- Interactive resizing requires
columnSizingFeatureandcolumnResizingFeature; fixed sizing needs only sizing. columnSizingInfobecomescolumnResizing.setColumnSizingInfo()becomessetColumnResizing().onColumnSizingInfoChangebecomesonColumnResizingChange.
Sorting, rows, and selection
| v8 | v9 |
|---|---|
sortingFn |
sortFn |
sortingFns |
sortFns |
getSortingFn() |
getSortFn() |
getAutoSortingFn() |
getAutoSortFn() |
SortingFn / SortingFns |
SortFn / SortFns |
row._getAllCellsByColumnId() |
row.getAllCellsByColumnId() |
Other _-prefixed internals are removed, including _getPinnedRows, _getFacetedRowModel, _getFacetedMinMaxValues, and _getFacetedUniqueValues.
getIsSomeRowsSelected() and getIsSomePageRowsSelected() mean at least one, including all. Use getIsSomeRowsSelected() && !getIsAllRowsSelected() or getIsSomePageRowsSelected() && !getIsAllPageRowsSelected() for indeterminate UI.
TypeScript Migration
- Add
TFeaturesfirst:ColumnDef<typeof features, Person>,Column<typeof features, Person>,Row<typeof features, Person>,Table<typeof features, Person>. - Replace
createColumnHelper<Person>()withcreateColumnHelper<typeof features, Person>(); usecolumnHelper.columns([...])for inference. - Use
StockFeatureswhen usingstockFeatures. - Existing
TableMeta/ColumnMetadeclaration merging must addTFeaturesfirst. Prefer per-tabletableMeta/columnMeta: metaHelper<...>()slots. - Replace global
FilterFns,SortFns,AggregationFns, andFilterMetaaugmentation with registry slots andfilterMeta: metaHelper<...>(); registered keys become valid strings. RowDatais restricted to records or arrays; prefer explicit object row types.
Common Migration Failures
CRITICAL: Keeping the v8 controller shape
The controller constructor takes only the host in v9. Pass options to .table(...) while rendering.
HIGH: Recreating the controller in render
Keep one stable controller field so subscriptions and host lifecycle remain attached.
HIGH: Leaving row models on table options
Move each row model beside its prerequisite feature in tableFeatures.
HIGH: Unstable table.subscribe selector
Define the selector as a class field or outside render to prevent avoidable update churn.
HIGH: Destructuring instance methods
Use row.getValue('name'); prototype methods require the original instance and are absent from shallow clones.
Final Checklist
-
TableControlleris host-only, stable, typed with features/data, and options move to.table(...). - Features, row models, and registries are in
tableFeatures; core row model is removed. - State reads use selected state, atoms, or store intentionally; selectors are stable.
-
onStateChangeis replaced; controlled and external-atom ownership do not overlap. - Rendering uses the v9
FlexRenderobject helpers. - Prototype methods, pinning, sizing/resizing, sorting, row, and selection changes are audited.
- Helpers, types, meta, registries, and
RowDatause v9 shapes. - Temporary
stockFeaturesusage has an explicit removal plan.
API Discovery
Inspect node_modules/@tanstack/lit-table/dist/index.d.ts and TableController.d.ts. Verify feature slots and the exact installed v9 APIs in node_modules/@tanstack/table-core/dist/; do not reconstruct v9 from v8 memory.