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 stockFeatures as a temporary audit bridge; explicit features are the production target.
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().sorting becomes table.state.sorting, table.store.state.sorting, or narrow table.atoms.sorting.get().
table.state contains all registered slices by default. Pass a second-argument selector to controller.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 matching on[State]Change callbacks that resolve value-or-function updaters.
Top-level onStateChange is removed. Use per-slice callbacks, external atoms, or table.store.subscribe for all changes.
External atoms come from @tanstack/store and are provided through atoms. Never provide both atoms.pagination and state.pagination.
The controller requests host updates for table and option-store changes; do not create a new controller in render.
Treat table.baseAtoms as 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.
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.
Replace global FilterFns, SortFns, AggregationFns, and FilterMeta augmentation with registry slots and filterMeta: metaHelper<...>(); registered keys become valid strings.
RowData is 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
TableController is 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.
onStateChange is replaced; controlled and external-atom ownership do not overlap.
Rendering uses the v9 FlexRender object helpers.
Prototype methods, pinning, sizing/resizing, sorting, row, and selection changes are audited.
Helpers, types, meta, registries, and RowData use v9 shapes.
Temporary stockFeatures usage 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.
1---2name: tanstack-lit-table-migrate-v8-to-v93description: Complete Lit v8-to-v9 migration reference: TableController construction, explicit features and row-model slots, selected/atom state, FlexRender, createTableHook, type generics, prototype methods, sorting, sizing, selection, and logical pinning.4license: MIT5---67Use 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.89Framework prerequisites: Lit 3.1.3 or newer within v3 (`lit ^3.1.3`) and10`@lit/context ^1.1.0`.1112## Recommended Migration Order13141. Replace the v8 controller options thunk with a host-only typed `TableController`.152. Pass current options to `controller.table(options, selector?)` in render.163. Move features, row models, and registries into `tableFeatures`.174. Update selected state, controlled ownership, and rendering.185. Apply every shared API and type rename below.196. Treat `stockFeatures` as a temporary audit bridge; explicit features are the production target.2021```ts22const features = tableFeatures({23 rowSortingFeature,24 sortedRowModel: createSortedRowModel(),25 sortFns: { alphanumeric: sortFn_alphanumeric },26})2728class PeopleTable extends LitElement {29 private controller = new TableController<typeof features, Person>(this)3031 protected render() {32 const table = this.controller.table({ features, columns, data: this.data })33 return html`<span>${table.getRowModel().rows.length} rows</span>`34 }35}36```3738## Construction and Feature Registration3940| v8 | v9 |41| ------------------------------------------ | ---------------------------------------------------------- |42| `new TableController(this, () => options)` | `new TableController<typeof features, TData>(this)` |43| `controller.table` property | `controller.table(options, selector?)` call in render |44| All features bundled | Required `features: tableFeatures({...})` |45| `getCoreRowModel()` option | Remove; core row model is automatic |46| `get*RowModel()` table options | `create*RowModel()` slots in `tableFeatures` |47| `sortingFns` table option | `sortFns` feature slot |48| Top-level `onStateChange` | Per-slice callbacks, external atoms, or store subscription |4950Feature 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.5152### Row-model mapping5354| v8 option | v9 slot and factory |55| -------------------------- | ------------------------------------------------------------------- |56| `getFilteredRowModel()` | `filteredRowModel: createFilteredRowModel()` after column filtering |57| `getSortedRowModel()` | `sortedRowModel: createSortedRowModel()` after row sorting |58| `getPaginationRowModel()` | `paginatedRowModel: createPaginatedRowModel()` after pagination |59| `getExpandedRowModel()` | `expandedRowModel: createExpandedRowModel()` after expanding |60| `getGroupedRowModel()` | `groupedRowModel: createGroupedRowModel()` after grouping |61| `getFacetedRowModel()` | `facetedRowModel: createFacetedRowModel()` after faceting |62| `getFacetedMinMaxValues()` | `facetedMinMaxValues: createFacetedMinMaxValues()` |63| `getFacetedUniqueValues()` | `facetedUniqueValues: createFacetedUniqueValues()` |6465Factories 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.6667## Lit State Migration6869- `table.getState().sorting` becomes `table.state.sorting`, `table.store.state.sorting`, or narrow `table.atoms.sorting.get()`.70- `table.state` contains all registered slices by default. Pass a second-argument selector to `controller.table(...)` only to narrow the render-selected surface.71- Use `table.subscribe(table.store, stableSelector, renderCallback)` for selected template state. Keep the selector reference stable outside render.72- Controlled state uses Lit `@state()` fields and matching `on[State]Change` callbacks that resolve value-or-function updaters.73- Top-level `onStateChange` is removed. Use per-slice callbacks, external atoms, or `table.store.subscribe` for all changes.74- External atoms come from `@tanstack/store` and are provided through `atoms`. Never provide both `atoms.pagination` and `state.pagination`.75- The controller requests host updates for table and option-store changes; do not create a new controller in render.76- Treat `table.baseAtoms` as internal writable state; prefer feature APIs or external atoms.7778## Rendering and Composition7980| v8 | v9 |81| -------------------------- | ----------------------------------------------------------------------------- |82| `flexRender(def, context)` | `FlexRender({ cell })`, `FlexRender({ header })`, or `FlexRender({ footer })` |83| Standalone helper only | `table.FlexRender({ cell })` is also available |84| Repeated raw options | `tableOptions(...)` composition |85| Repeated conventions | `createTableHook({ features, ... })` |8687`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.8889## Complete Shared Breaking-Change Map9091### Instance methods9293Row, 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.9495### Logical column pinning9697V9 has no `left`/`right` aliases.9899| old | new |100| -------------------------------------------------------------- | ------------------------------------------------------------- |101| `columnPinning.left` / `.right` | `.start` / `.end` |102| `column.pin('left' \| 'right')` | `column.pin('start' \| 'end')` |103| `getIsPinned() === 'left' \| 'right'` | `'start' \| 'end'` |104| `row.getLeftVisibleCells()` / `getRightVisibleCells()` | `getStartVisibleCells()` / `getEndVisibleCells()` |105| `getLeftHeaderGroups()` / `getRightHeaderGroups()` | `getStartHeaderGroups()` / `getEndHeaderGroups()` |106| `getLeftFooterGroups()` / `getRightFooterGroups()` | `getStartFooterGroups()` / `getEndFooterGroups()` |107| `getLeftFlatHeaders()` / `getRightFlatHeaders()` | `getStartFlatHeaders()` / `getEndFlatHeaders()` |108| `getLeftLeafHeaders()` / `getRightLeafHeaders()` | `getStartLeafHeaders()` / `getEndLeafHeaders()` |109| `getLeftLeafColumns()` / `getRightLeafColumns()` | `getStartLeafColumns()` / `getEndLeafColumns()` |110| `getLeftVisibleLeafColumns()` / `getRightVisibleLeafColumns()` | `getStartVisibleLeafColumns()` / `getEndVisibleLeafColumns()` |111| `getLeftTotalSize()` / `getRightTotalSize()` | `getStartTotalSize()` / `getEndTotalSize()` |112| `column.getStart('left')` | `column.getStart('start')` |113| `column.getAfter('right')` | `column.getAfter('end')` |114| `column.getIndex('left' \| 'right')` | `column.getIndex('start' \| 'end')` |115116Prefer CSS logical inset properties; logical names do not set DOM direction. `columnResizeDirection` is unchanged.117118### Pinning, sizing, and resizing119120- `enablePinning` splits into `enableColumnPinning` and `enableRowPinning`.121- Interactive resizing requires `columnSizingFeature` and `columnResizingFeature`; fixed sizing needs only sizing.122- `columnSizingInfo` becomes `columnResizing`.123- `setColumnSizingInfo()` becomes `setColumnResizing()`.124- `onColumnSizingInfoChange` becomes `onColumnResizingChange`.125126### Sorting, rows, and selection127128| v8 | v9 |129| ------------------------------ | ----------------------------- |130| `sortingFn` | `sortFn` |131| `sortingFns` | `sortFns` |132| `getSortingFn()` | `getSortFn()` |133| `getAutoSortingFn()` | `getAutoSortFn()` |134| `SortingFn` / `SortingFns` | `SortFn` / `SortFns` |135| `row._getAllCellsByColumnId()` | `row.getAllCellsByColumnId()` |136137Other `_`-prefixed internals are removed, including `_getPinnedRows`, `_getFacetedRowModel`, `_getFacetedMinMaxValues`, and `_getFacetedUniqueValues`.138139`getIsSomeRowsSelected()` and `getIsSomePageRowsSelected()` mean at least one, including all. Use `getIsSomeRowsSelected() && !getIsAllRowsSelected()` or `getIsSomePageRowsSelected() && !getIsAllPageRowsSelected()` for indeterminate UI.140141## TypeScript Migration142143- Add `TFeatures` first: `ColumnDef<typeof features, Person>`, `Column<typeof features, Person>`, `Row<typeof features, Person>`, `Table<typeof features, Person>`.144- Replace `createColumnHelper<Person>()` with `createColumnHelper<typeof features, Person>()`; use `columnHelper.columns([...])` for inference.145- Use `StockFeatures` when using `stockFeatures`.146- Existing `TableMeta`/`ColumnMeta` declaration merging must add `TFeatures` first. Prefer per-table `tableMeta`/`columnMeta: metaHelper<...>()` slots.147- Replace global `FilterFns`, `SortFns`, `AggregationFns`, and `FilterMeta` augmentation with registry slots and `filterMeta: metaHelper<...>()`; registered keys become valid strings.148- `RowData` is restricted to records or arrays; prefer explicit object row types.149150## Common Migration Failures151152### CRITICAL: Keeping the v8 controller shape153154The controller constructor takes only the host in v9. Pass options to `.table(...)` while rendering.155156### HIGH: Recreating the controller in render157158Keep one stable controller field so subscriptions and host lifecycle remain attached.159160### HIGH: Leaving row models on table options161162Move each row model beside its prerequisite feature in `tableFeatures`.163164### HIGH: Unstable table.subscribe selector165166Define the selector as a class field or outside render to prevent avoidable update churn.167168### HIGH: Destructuring instance methods169170Use `row.getValue('name')`; prototype methods require the original instance and are absent from shallow clones.171172## Final Checklist173174- [ ] `TableController` is host-only, stable, typed with features/data, and options move to `.table(...)`.175- [ ] Features, row models, and registries are in `tableFeatures`; core row model is removed.176- [ ] State reads use selected state, atoms, or store intentionally; selectors are stable.177- [ ] `onStateChange` is replaced; controlled and external-atom ownership do not overlap.178- [ ] Rendering uses the v9 `FlexRender` object helpers.179- [ ] Prototype methods, pinning, sizing/resizing, sorting, row, and selection changes are audited.180- [ ] Helpers, types, meta, registries, and `RowData` use v9 shapes.181- [ ] Temporary `stockFeatures` usage has an explicit removal plan.182183## API Discovery184185Inspect `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.
Run npx skillmds@latest add lukasa1993/tanstack-lit-table-migrate-v8-to-v9 in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Complete Lit v8-to-v9 migration reference: TableController construction, explicit features and row-model slots, selected/atom state, FlexRender, createTableHook, type generics, prototype methods, sorting, sizing, selection, and logical pinning. It is listed under AI & ML on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
lukasa1993 (@lukasa1993) published this skill. Their other Agent Skills are listed on their SkillMD profile.