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.
62
74%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./packages/lit-table/skills/migrate-v8-to-v9/SKILL.mdUse 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.
TableController.controller.table(options, selector?) in render.tableFeatures.stockFeatures as 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>`
}
}| 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.
| 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.
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.table.subscribe(table.store, stableSelector, renderCallback) for selected template state. Keep the selector reference stable outside render.@state() fields and matching on[State]Change callbacks that resolve value-or-function updaters.onStateChange is removed. Use per-slice callbacks, external atoms, or table.store.subscribe for all changes.@tanstack/store and are provided through atoms. Never provide both atoms.pagination and state.pagination.table.baseAtoms as internal writable state; prefer feature APIs or external atoms.| 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.
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.
Beta.38 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.
enablePinning splits into enableColumnPinning and enableRowPinning.columnSizingFeature and columnResizingFeature; fixed sizing needs only sizing.columnSizingInfo becomes columnResizing.setColumnSizingInfo() becomes setColumnResizing().onColumnSizingInfoChange becomes onColumnResizingChange.| 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.
TFeatures first: ColumnDef<typeof features, Person>, Column<typeof features, Person>, Row<typeof features, Person>, Table<typeof features, Person>.createColumnHelper<Person>() with createColumnHelper<typeof features, Person>(); use columnHelper.columns([...]) for inference.StockFeatures when using stockFeatures.TableMeta/ColumnMeta declaration merging must add TFeatures first. Prefer per-table tableMeta/columnMeta: metaHelper<...>() slots.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.The controller constructor takes only the host in v9. Pass options to .table(...) while rendering.
Keep one stable controller field so subscriptions and host lifecycle remain attached.
Move each row model beside its prerequisite feature in tableFeatures.
Define the selector as a class field or outside render to prevent avoidable update churn.
Use row.getValue('name'); prototype methods require the original instance and are absent from shallow clones.
TableController is host-only, stable, typed with features/data, and options move to .table(...).tableFeatures; core row model is removed.onStateChange is replaced; controlled and external-atom ownership do not overlap.FlexRender object helpers.RowData use v9 shapes.stockFeatures usage has an explicit removal plan.Inspect node_modules/@tanstack/lit-table/dist/index.d.ts and TableController.d.ts. Verify feature slots and exact beta APIs in node_modules/@tanstack/table-core/dist/; do not reconstruct v9 from v8 memory.
9e523bc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.