diff --git a/_artifacts/domain_map.yaml b/_artifacts/domain_map.yaml
index 70359e636e..d68d818d69 100644
--- a/_artifacts/domain_map.yaml
+++ b/_artifacts/domain_map.yaml
@@ -11,7 +11,7 @@ library:
meta:
generated_by: '@tanstack/intent scaffold domain discovery'
- date: '2026-07-10'
+ date: '2026-07-29'
status: reviewed
maintainer_review_pending: false
phase_4_date: '2026-07-10'
@@ -892,7 +892,7 @@ skills:
domain: framework-adapters
type: framework
framework: svelte
- purpose: 'Create a Svelte 5 table with createTable, rune-backed options, snippets, and headless markup.'
+ purpose: 'Create a Svelte 5 table with createTable, rune-backed option getters, FlexRender, and headless markup.'
sources:
[
'TanStack/table:docs/framework/svelte/guide/migrating.md',
@@ -909,16 +909,19 @@ skills:
domain: framework-adapters
type: framework
framework: svelte
- purpose: 'Use rune-backed atoms, selected table.state, controlled slices, and external atoms without reactivity mismatches.'
+ purpose: 'Use rune-aware table atoms and stores, native $derived projections, controlled $state or createTableState slices, and external atoms without broad invalidation or snapshot mismatches.'
sources:
[
'TanStack/table:docs/framework/svelte/guide/table-state.md',
'TanStack/table:docs/framework/svelte/guide/pagination.md',
'TanStack/table:examples/svelte/basic-external-state',
'TanStack/table:packages/svelte-table/src/createTable.svelte.ts',
+ 'TanStack/table:packages/svelte-table/src/createTableState.svelte.ts',
]
failure_modes:
- - 'Reading or passing non-reactive snapshots where createTable options need getters over $state or $derived values.'
+ - 'Keeping the removed beta.58 createTable/createAppTable selector argument, selected table.state property, subscribeTable helper, or SubscribeSource type after beta.59.'
+ - 'Reading atom or store snapshots outside a tracked Svelte scope and expecting the read itself to keep a consumer reactive.'
+ - 'Reading table.store.get() in an effect that only needs one atom slice, causing unrelated state changes to rerun the effect.'
- 'Pairing onSliceChange with a controlled state slice that is not actually written back with value-or-updater semantics.'
- 'Blaming pagination reactivity when autoResetPageIndex immediately overwrites an externally requested page.'
@@ -937,6 +940,7 @@ skills:
failure_modes:
- 'Attempting the v9 adapter migration without first adopting Svelte 5-compatible component and reactivity syntax.'
- 'Keeping readable-store assumptions or v8 row-model table options after switching to createTable.'
+ - 'Keeping beta.58 creation selectors, table.state, subscribeTable, or their removed selected-state generic parameters after beta.59.'
- 'Missing shared v9 prototype, feature, helper, sorting, and logical-pinning changes.'
- slug: create-table-hook
@@ -952,7 +956,7 @@ skills:
'TanStack/table:packages/svelte-table/src/createTableHook.svelte.ts',
]
failure_modes:
- - 'Defining the hook in a plain module that cannot carry the required Svelte rune semantics.'
+ - 'Reimplementing createAppTable around the framework-agnostic core instead of using the shipped rune-capable createTableHook implementation.'
- 'Passing $state snapshots instead of getters to createAppTable, freezing data or controlled state.'
- 'Prop-drilling contexts through registered components instead of consuming the typed hooks under matching App wrappers.'
diff --git a/_artifacts/skill_spec.md b/_artifacts/skill_spec.md
index 6f45dd3c24..5868132346 100644
--- a/_artifacts/skill_spec.md
+++ b/_artifacts/skill_spec.md
@@ -1,8 +1,8 @@
# TanStack Table v9 skill specification
-Status: reviewed
-Date: 2026-07-10
-Library target: TanStack Table v9, authored in stable-release voice
+Status: reviewed
+Date: 2026-07-29
+Library target: TanStack Table v9, authored in stable-release voice
Package metadata target: exact workspace package versions; release automation keeps every shipped skill synchronized
This specification is the generation contract for a deliberately smaller, foot-gun-first TanStack Intent skill set. It is not a documentation outline. The complete evidence and failure-mode inventory is in domain_map.yaml.
@@ -305,8 +305,11 @@ All Devtools skills must emphasize the required non-empty table options.key, lif
### Svelte
- V9 targets Svelte 5 and runes.
+- Read a slice with `table.atoms..get()` and the complete state with `table.store.get()` inside templates, `$derived`, `$derived.by`, or `$effect`; reads outside tracked scopes are current snapshots.
+- Starting in beta.59, Svelte has no table-creation selector, selected `table.state`, `subscribeTable`, or `SubscribeSource`; use native `$derived` projections instead.
+- Prefer `$state` plus getter-backed controlled slices, or `createTableState` for an updater-compatible getter/setter pair. Keep `useSelector` only for raw external atoms consumed outside the table.
- Reactive data and controlled values commonly need getters; avoid passing snapshots.
-- createTableHook implementation belongs in a rune-capable module.
+- The shipped `createTableHook` implementation supplies rune semantics, so an app hook that calls it may live in a normal `.ts` module.
### Vue
@@ -342,7 +345,7 @@ All Devtools skills must emphasize the required non-empty table options.key, lif
## Cross-cutting placement rules
-- Performance: stable inputs in getting-started/core; selectors in table-state; CSS variables in resizing; measurement/overscan in Virtual; row ownership in client-vs-server.
+- Performance: stable inputs in getting-started/core; adapter-specific fine-grained state reads or selectors in table-state; CSS variables in resizing; measurement/overscan in Virtual; row ownership in client-vs-server.
- CSS: pinning, sizing, resizing, and Virtual skills only. Core may state that CSS is user-owned.
- Accessibility: core/getting-started may remind that headless rendering leaves semantics and interaction accessibility with the renderer; do not create a component-library integration skill.
- Query: data source and manual processing boundaries, not Table rendering.
diff --git a/_artifacts/skill_tree.yaml b/_artifacts/skill_tree.yaml
index 9f50dbf2f8..5d06c586fb 100644
--- a/_artifacts/skill_tree.yaml
+++ b/_artifacts/skill_tree.yaml
@@ -8,9 +8,9 @@ library:
generated_from:
domain_map: '_artifacts/domain_map.yaml'
skill_spec: '_artifacts/skill_spec.md'
-generated_at: '2026-07-10'
+generated_at: '2026-07-29'
status: reviewed
-reviewed_at: '2026-07-10'
+reviewed_at: '2026-07-29'
batch_review: true
structure: 'flat-per-package'
monorepo_layout: true
@@ -607,7 +607,7 @@ skills:
domain: framework-adapters
path: 'packages/svelte-table/skills/getting-started/SKILL.md'
package: 'packages/svelte-table'
- description: 'Create a Svelte 5 TanStack Table v9 table with createTable, explicit tableFeatures, rune-backed data getters, stable static inputs, snippets, and headless markup. Load when replacing createSvelteTable or pre-rune patterns.'
+ description: 'Create a Svelte 5 TanStack Table v9 table with createTable, explicit tableFeatures, rune-backed data getters, stable static inputs, FlexRender, and headless markup. Load when replacing createSvelteTable or pre-rune patterns.'
requires:
['@tanstack/table-core#core', '@tanstack/table-core#table-features']
sources:
@@ -621,13 +621,14 @@ skills:
domain: framework-adapters
path: 'packages/svelte-table/skills/table-state/SKILL.md'
package: 'packages/svelte-table'
- description: 'Use Svelte 5 rune-backed table.atoms/store and selected table.state, reactive option getters, controlled $state slices, value-or-updater callbacks, external atoms, and auto-reset behavior without snapshot mismatches.'
+ description: 'Use Svelte 5 rune-aware table atoms and stores, $derived projections, reactive option getters, controlled $state or createTableState slices, external atoms, and auto-reset behavior without broad invalidation or snapshot mismatches.'
requires: ['@tanstack/table-core#core', 'getting-started']
sources:
- 'TanStack/table:docs/framework/svelte/guide/table-state.md'
+ - 'TanStack/table:docs/framework/svelte/guide/pagination.md'
- 'TanStack/table:examples/svelte/basic-external-state'
- 'TanStack/table:packages/svelte-table/src/createTable.svelte.ts'
- - 'TanStack/table:docs/framework/svelte/guide/pagination.md'
+ - 'TanStack/table:packages/svelte-table/src/createTableState.svelte.ts'
- name: 'Svelte Migrate v8 to v9'
slug: migrate-v8-to-v9
@@ -635,7 +636,7 @@ skills:
domain: framework-adapters
path: 'packages/svelte-table/skills/migrate-v8-to-v9/SKILL.md'
package: 'packages/svelte-table'
- description: 'Migrate Svelte v8/store-oriented tables to the Svelte 5 v9 createTable adapter, runes, explicit feature and row-model slots, current rendering, prototype methods, sortFn names, helpers, and logical pinning.'
+ description: 'Complete Svelte v8-to-v9 migration reference: Svelte 5, createTable, beta.59 selector removal, explicit features and row-model slots, atom/rune state, rendering helpers, prototype methods, type generics, sorting, sizing, selection, and logical pinning.'
requires:
[
'@tanstack/table-core#migrate-v8-to-v9',
@@ -653,7 +654,7 @@ skills:
domain: framework-adapters
path: 'packages/svelte-table/skills/create-table-hook/SKILL.md'
package: 'packages/svelte-table'
- description: 'Define a Svelte createAppTable/createAppColumnHelper in a rune-capable module with shared features/defaults, reactive per-table getters, optional App component registries, and typed table/cell/header context hooks.'
+ description: "Define a Svelte createAppTable/createAppColumnHelper using the adapter's rune-capable createTableHook implementation, shared features/defaults, reactive per-table getters, optional App component registries, and typed table/cell/header context hooks."
requires: ['@tanstack/table-core#core', 'getting-started', 'table-state']
sources:
- 'TanStack/table:docs/framework/svelte/guide/composable-tables.md'
diff --git a/docs/config.json b/docs/config.json
index c4db3fdcc0..966d3ca75c 100644
--- a/docs/config.json
+++ b/docs/config.json
@@ -590,7 +590,6 @@
{ "label": "createTableState", "to": "framework/svelte/reference/functions/createTableState" },
{ "label": "SvelteTable", "to": "framework/svelte/reference/type-aliases/SvelteTable" },
{ "label": "AppSvelteTable", "to": "framework/svelte/reference/type-aliases/AppSvelteTable" },
- { "label": "subscribeTable", "to": "framework/svelte/reference/functions/subscribeTable" },
{ "label": "renderComponent", "to": "framework/svelte/reference/functions/renderComponent" },
{ "label": "renderSnippet", "to": "framework/svelte/reference/functions/renderSnippet" },
{ "label": "FlexRender", "to": "framework/svelte/reference/variables/FlexRender" }
diff --git a/docs/framework/svelte/guide/cell-selection.md b/docs/framework/svelte/guide/cell-selection.md
index 949b9d8de6..03e47b3681 100644
--- a/docs/framework/svelte/guide/cell-selection.md
+++ b/docs/framework/svelte/guide/cell-selection.md
@@ -38,20 +38,27 @@ The cell selection feature keeps track of spreadsheet-style rectangular selectio
The table instance already manages the cell selection state for you. You can access the selection or values derived from it through a few APIs.
-- `table.state.cellSelection` - returns the cell selection state reactively (selected by the `createTable` selector)
+- `table.atoms.cellSelection.get()` - returns the current cell selection state and participates in Svelte tracking when read in a template or rune
- `getSelectedCellCount()` - returns how many cells are selected
- `getSelectedCellIds()` - returns the ids of every selected cell
- `getCellSelectionRowIds()` / `getCellSelectionColumnIds()` - returns the rows and columns the selection touches
- `getSelectedCellRangesData()` - returns each selected rectangle's values as a row-major grid
```ts
-console.log(table.state.cellSelection) //get the cell selection state
+console.log(table.atoms.cellSelection.get()) //get the cell selection state
console.log(table.getSelectedCellCount()) //3
console.log(table.getSelectedCellIds()) //['0_firstName', '0_lastName', '1_firstName']
console.log(table.getSelectedCellRangesData()) //[[['Tanner', 'Linsley'], ['Kevin', 'Vandy']]]
```
-In event handlers or other non-render code, you can also read the current snapshot with `table.atoms.cellSelection.get()`. This read does not subscribe a component to future changes, so prefer `table.state.cellSelection` in render positions.
+Use the same atom read in markup or a native derived value:
+
+```ts
+const cellSelection = $derived(table.atoms.cellSelection.get())
+const selectedRangeCount = $derived(cellSelection.length)
+```
+
+Outside a tracked context, `table.atoms.cellSelection.get()` is simply the current snapshot.
The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoized and pull-based. They cost nothing unless you actually call them, so a table that only highlights cells never pays to enumerate a large selection.
@@ -313,8 +320,8 @@ Because a reorder can widen a selection onto columns the user never picked, some
let isFirstColumnLayout = true
$effect(() => {
- // read the atoms, not table.state: the selector rebuilds that object on every
- // selected slice change, so reading it here would re-fire on every selection
+ // Read only the layout atoms. A full table.store.get() read would re-run for
+ // every table state change, including selection changes.
void table.atoms.columnOrder.get()
void table.atoms.columnPinning.get()
void table.atoms.columnVisibility.get()
@@ -343,21 +350,8 @@ const table = createTable({
### Performance
-Svelte's runes track these reads and the compiler updates only the DOM nodes
-that actually changed, so the example renders its cells plainly.
+Svelte's runes track atom reads and the compiler updates only the DOM nodes that actually changed, so the example renders its cells plainly.
-Measured on a table with a thousand rows and twelve columns, a drag updates in
-roughly 16ms per move with plain reads.
-
-`subscribeTable` is available if you want an explicit fine-grained subscription:
-
-```svelte
-const selected = subscribeTable( table.atoms.cellSelection, (ranges) =>
-ranges.length, ) // read it as selected.current
-```
+Measured on a table with a thousand rows and twelve columns, a drag updates in roughly 16ms per move with plain reads.
-One trap worth knowing: do not read `table.state` inside an `$effect` that also
-writes selection state. The selector rebuilds that object whenever any selected
-slice changes, so an effect meant to watch the column layout will re-fire on
-every selection change and clear the selection you just made. Read
-`table.atoms..get()` instead.
+One trap worth knowing: do not read `table.store.get()` inside an `$effect` that also writes selection state unless the effect truly depends on every state slice. An effect meant to watch column layout would otherwise re-run on every selection change and clear the selection you just made. Read only the required `table.atoms..get()` values instead.
diff --git a/docs/framework/svelte/guide/column-filtering.md b/docs/framework/svelte/guide/column-filtering.md
index 4240a13eb3..1972fb7c90 100644
--- a/docs/framework/svelte/guide/column-filtering.md
+++ b/docs/framework/svelte/guide/column-filtering.md
@@ -138,7 +138,7 @@ Since the column filter state is an array of objects, you can have multiple colu
#### Accessing Column Filter State
-For reactive reads that should update your UI, use `table.state.columnFilters` (selected by the `createTable` selector) or `subscribeTable`. In event handlers or other non-reactive code, you can read the current snapshot with `table.atoms.columnFilters.get()`, but this read only participates in Svelte dependency tracking when called in a rune-tracked context.
+Read column filters from `table.atoms.columnFilters.get()`. The call returns the current value in event handlers and becomes reactive when it runs in a template, `$derived`, `$derived.by`, or `$effect`.
```ts
const table = createTable({
@@ -148,8 +148,11 @@ const table = createTable({
//...
})
-table.state.columnFilters // reactive read
-table.atoms.columnFilters.get() // snapshot read in event handlers
+const columnFilters = $derived(table.atoms.columnFilters.get())
+
+const logCurrentColumnFilters = () => {
+ console.log(table.atoms.columnFilters.get())
+}
```
However, if you need access to the column filter state outside of the table, you can "control" the column filter state like down below.
diff --git a/docs/framework/svelte/guide/column-resizing.md b/docs/framework/svelte/guide/column-resizing.md
index 13e78f2bd3..1b14e6fbc2 100644
--- a/docs/framework/svelte/guide/column-resizing.md
+++ b/docs/framework/svelte/guide/column-resizing.md
@@ -148,7 +148,7 @@ TanStack Table keeps track of a `columnResizing` state object that you can use t
```
@@ -253,4 +253,4 @@ Svelte 5's fine-grained reactivity means a resize does not re-render whole compo
1. **Derive all column widths in one `$derived` value.** Build a single style string that maps each header and column id to a CSS variable, then bind it on the `
` element (`
`). `header.getSize()` reads the rune-backed `columnSizing` atom, so a drag re-runs only this derived and its single attribute effect.
2. **Reference the variables in cell styles** (`width: calc(var(--col-firstName-size) * 1px)`) so the browser applies new widths without Svelte re-evaluating each cell.
-Because nothing in the table body reads resize state, no body memoization is needed, and you do not need a `createTable` selector for this pattern; the resizer's highlight is its own inline binding.
+Because nothing in the table body reads resize state, no body memoization is needed; the resizer's highlight is its own narrow inline atom binding.
diff --git a/docs/framework/svelte/guide/composable-tables.md b/docs/framework/svelte/guide/composable-tables.md
index 584637319f..e126d0602e 100644
--- a/docs/framework/svelte/guide/composable-tables.md
+++ b/docs/framework/svelte/guide/composable-tables.md
@@ -276,13 +276,9 @@ Create each table with `createAppTable`. In Svelte 5, pass reactive data through
debugTable: true,
})
- let sorting = $derived(table.state.sorting)
- let columnFilters = $derived(table.state.columnFilters)
-
- const rows = $derived.by(() => {
- JSON.stringify(table.state)
- return table.getRowModel().rows
- })
+ const sorting = $derived(table.atoms.sorting.get())
+ const columnFilters = $derived(table.atoms.columnFilters.get())
+ const rows = $derived(table.getRowModel().rows)
```
diff --git a/docs/framework/svelte/guide/custom-features.md b/docs/framework/svelte/guide/custom-features.md
index 509d91c658..f2afbb548e 100644
--- a/docs/framework/svelte/guide/custom-features.md
+++ b/docs/framework/svelte/guide/custom-features.md
@@ -330,10 +330,10 @@ const table = createTable({
### Step 5: Use the Feature in Your Application
-Now that the feature is added to the table instance, you can use the new instance APIs, options, and state in your application. `table.state.density` reads the new state slice reactively in your markup, and `table.setDensity` / `table.toggleDensity` update it.
+Now that the feature is added to the table instance, you can use the new instance APIs, options, and state in your application. `table.atoms.density.get()` reads the new state slice reactively in your markup, and `table.setDensity` / `table.toggleDensity` update it.
```svelte
-{@const density = table.state.density}
+{@const density = table.atoms.density.get()}
table.setGlobalFilter((e.target as HTMLInputElement).value)}
placeholder="Search all columns..."
/>
diff --git a/docs/framework/svelte/guide/migrating.md b/docs/framework/svelte/guide/migrating.md
index 030ab394a9..c2cd59e6ab 100644
--- a/docs/framework/svelte/guide/migrating.md
+++ b/docs/framework/svelte/guide/migrating.md
@@ -2,6 +2,9 @@
title: Migrating to TanStack Table V9 (Svelte)
---
+> [!NOTE]
+> `v9.0.0-beta.59` removes Svelte's table-creation selectors. `createTable` and `createAppTable` now accept only their options object; the selected `table.state` property, `subscribeTable`, and `SubscribeSource` are also removed. Read one slice with `table.atoms..get()` and the complete state with `table.store.get()`. These reads update naturally in Svelte templates, `$derived`, `$derived.by`, and `$effect`. This is an intentional beta-breaking change with no compatibility overload or runtime shim. See [State Management Changes](#state-management-changes) for examples.
+
> [!NOTE]
> `v9.0.0-beta.48`/`beta.49` split aggregation out of `columnGroupingFeature` into a new `rowAggregationFeature` (`stockFeatures` includes both). If you declare features explicitly, add `rowAggregationFeature` anywhere you use `aggregationFns`, `aggregationFn`, `aggregatedCell`, `cell.getIsAggregated()`, or `column.getAggregationValue()`. Aggregation function definitions, row-depth selection, and the `getAggregationValue` signature also changed. See [Grouping and Aggregation](#grouping-and-aggregation) below.
@@ -25,7 +28,7 @@ TanStack Table V9 is a major release with significant internal architectural imp
- **TanStack Store foundation**: Table state is backed by TanStack Store atoms.
- **Svelte 5 reactivity**: The adapter uses Svelte 5 runes and Svelte-aware atom bindings.
-- **Fine-grained subscriptions**: `table.state` contains the full registered state by default; use a custom selector, `subscribeTable`, or `useSelector` from `@tanstack/svelte-store` when you need a narrower reactive surface.
+- **Native fine-grained reads**: Use `table.atoms..get()` for narrow state and native `$derived` values for projections. Use `table.store.get()` when a computation intentionally needs the complete state.
### 3. Type-Safety Improvements
@@ -314,13 +317,11 @@ Because these methods now live on the prototype, they also do not appear as own
Svelte v9 table state is atom-backed and rune-aware. Do not port v8 writable-store table option patterns directly except as "before" code.
-| Surface | Use |
-| ----------------------------------- | ---------------------------------------------------------------------------------------------------- |
-| `table.state` | Full registered table state by default, or selected state from the second argument to `createTable`. |
-| `table.store.state` | Current full table state snapshot. |
-| `table.atoms..get()` | Narrow current-value read for one state slice. |
-| `subscribeTable(source, selector?)` | Fine-grained Svelte subscription helper that exposes `.current`. |
-| `table.baseAtoms.` | Internal writable atoms. Prefer feature APIs or external atoms. |
+| Surface | Use |
+| --------------------------- | ------------------------------------------------------------------------------- |
+| `table.atoms..get()` | Narrow, rune-aware read for one registered state slice. |
+| `table.store.get()` | Rune-aware full-state read; intentionally updates for any table state change. |
+| `table.baseAtoms.` | Internal writable atoms. Prefer feature APIs or externally owned state instead. |
### Accessing State
@@ -328,69 +329,73 @@ Svelte v9 table state is atom-backed and rune-aware. Do not port v8 writable-sto
// v8
const sorting = table.getState().sorting
-// v9: full snapshot
-const sorting = table.store.state.sorting
-
-// v9: narrow atom read
+// v9: narrow read (preferred for one slice)
const sorting = table.atoms.sorting.get()
-```
-
-By default, `table.state` contains the full registered table state.
-```ts
-const table = createTable({
- features,
- columns,
- get data() {
- return data
- },
-})
-
-const { pagination, sorting } = table.state
+// v9: complete state
+const state = table.store.get()
```
-Pass a second-argument selector when you want `table.state` to contain only the values that should cause reactive updates.
+The same reads become reactive dependencies when they run in a Svelte template, `$derived`, `$derived.by`, or `$effect`:
-```ts
-const table = createTable(
- {
+```svelte
+
+
+Page {pagination.pageIndex + 1}
```
-Passing `(state) => state` is equivalent to the default selector and is no longer necessary.
+`$derived` now owns projection and equality behavior. A full-store read is appropriate for debug output, persistence, or computations that intentionally depend on every slice; use an atom when only one slice matters.
-### Fine-grained Updates with `subscribeTable`
+#### Migrating a `createTable` Selector
```ts
-import { subscribeTable } from '@tanstack/svelte-table'
+// Before beta.59
+const table = createTable(options, (state) => ({
+ pagination: state.pagination,
+}))
-const pagination = subscribeTable(table.atoms.pagination)
-const pageIndex = subscribeTable(
- table.atoms.pagination,
- (pagination) => pagination.pageIndex,
-)
+table.state.pagination
```
-```svelte
-
- Page {pagination.current.pageIndex + 1} of {table.getPageCount()}
-
+```ts
+// beta.59+
+const table = createTable(options)
+const pagination = $derived(table.atoms.pagination.get())
```
+Identity selectors and selectors returning `null` should simply be deleted. Atom and table API reads already register the relevant Svelte dependencies.
+
+#### Migrating a `createAppTable` Selector
+
+```ts
+// Before beta.59
+const table = createAppTable(options, (state) => ({
+ sorting: state.sorting,
+}))
+table.state.sorting
+
+// beta.59+
+const table = createAppTable(options)
+const sorting = $derived(table.atoms.sorting.get())
+```
+
+The selected-state generic was removed with the selector. `SvelteTable` now has two generic parameters, `AppSvelteTable` has five, and `useTableContext` only accepts its optional row-data generic.
+
### Controlled State
-The v8-style `state` plus `on[State]Change` pattern still works in v9 and is the most direct migration path, but [External Atoms](#external-atoms) (below) are the preferred v9 way to own state slices outside the table.
+The v8-style `state` plus `on[State]Change` pattern still works in v9 and is the most direct migration path. Prefer Svelte `$state` for state owned by a component. Use [External Atoms](#external-atoms) when the app must share raw TanStack Store atoms outside the table.
Use `createTableState` for Svelte-owned state slices that need to accept TanStack Table updater functions.
@@ -865,7 +870,10 @@ type Person = {
- [ ] Convert custom aggregation callables to `constructAggregationFn({ aggregate, merge? })` definitions.
- [ ] Replace Svelte 3/4 writable-store table patterns with runes and getters.
- [ ] Pass reactive `data` and controlled `state` slices through getters.
-- [ ] Replace `table.getState()` reads with `table.state`, `table.store.state`, `table.atoms..get()`, or `subscribeTable`.
+- [ ] Remove second-argument selectors from `createTable` and `createAppTable`.
+- [ ] Replace `table.state` and `table.getState()` reads with `table.atoms..get()` or `table.store.get()`.
+- [ ] Replace selector projections with native `$derived` values.
+- [ ] Remove `subscribeTable` / `SubscribeSource` imports and update `SvelteTable` / `AppSvelteTable` generic arity.
- [ ] Replace `onStateChange` with per-slice callbacks or external atoms.
- [ ] Replace `declare module` augmentation of `FilterFns`/`SortFns`/`AggregationFns` with registry-slot registration, and `FilterMeta` augmentation with the `filterMeta` slot.
- [ ] Add `typeof features` to column helpers and types.
diff --git a/docs/framework/svelte/guide/pagination.md b/docs/framework/svelte/guide/pagination.md
index 70386579c5..0463d37c37 100644
--- a/docs/framework/svelte/guide/pagination.md
+++ b/docs/framework/svelte/guide/pagination.md
@@ -126,7 +126,11 @@ The `pagination` state is an object that contains the following properties:
- `pageIndex`: The current page index (zero-based).
- `pageSize`: The current page size.
-For reactive reads that should update your UI, use `table.state.pagination` (selected by the `createTable` selector). In event handlers, you can read the current snapshot with `table.atoms.pagination.get()`, but this read only participates in Svelte dependency tracking when called in a rune-tracked context.
+Read pagination from `table.atoms.pagination.get()`. The call returns the current value in event handlers and becomes reactive when it runs in a template, `$derived`, `$derived.by`, or `$effect`.
+
+```ts
+const pagination = $derived(table.atoms.pagination.get())
+```
If you need access to the `pagination` state outside of the table (a server-side query key is the most common case), you can own the slice yourself. The recommended way in v9 is an external atom passed through the `atoms` table option. Atoms preserve fine-grained subscriptions, and the pagination value can be used elsewhere (such as in a query key) without coupling that code to the table instance.
@@ -258,7 +262,7 @@ There are several pagination table instance APIs that are useful for hooking up
-
diff --git a/examples/svelte/cell-selection/src/App.svelte b/examples/svelte/cell-selection/src/App.svelte
index 1435720cfa..945dacabbc 100644
--- a/examples/svelte/cell-selection/src/App.svelte
+++ b/examples/svelte/cell-selection/src/App.svelte
@@ -168,12 +168,6 @@
// isCellRangeSelectionEvent: event => Boolean(event.metaKey), // use Meta instead of Shift
debugTable: true,
},
- (state) => ({
- sorting: state.sorting,
- columnVisibility: state.columnVisibility,
- columnOrder: state.columnOrder,
- columnPinning: state.columnPinning,
- }),
)
const randomizeColumns = () =>
@@ -189,9 +183,8 @@
// customize this to your needs
let isFirstColumnLayout = true
$effect(() => {
- // read the atoms directly so this tracks only the layout slices. Reading
- // table.state here would re-fire on every selection change, because the
- // selector rebuilds that object each time any selected slice changes.
+ // Read only the layout atoms so selection changes do not re-run this
+ // effect. A full `table.store.get()` read would track every state slice.
void table.atoms.columnOrder.get()
void table.atoms.columnPinning.get()
void table.atoms.columnVisibility.get()
@@ -434,7 +427,7 @@
diff --git a/examples/svelte/column-visibility/tests/e2e/smoke.spec.ts b/examples/svelte/column-visibility/tests/e2e/smoke.spec.ts
index 4741f78eaa..51b6bec6d4 100644
--- a/examples/svelte/column-visibility/tests/e2e/smoke.spec.ts
+++ b/examples/svelte/column-visibility/tests/e2e/smoke.spec.ts
@@ -94,7 +94,7 @@ function firstBodyRowCells(page: Page) {
return page.locator('tbody tr').first().locator('td')
}
-/** Row data is random faker output, so `table.state` is the stable oracle. */
+/** Row data is random faker output, so the full state dump is the stable oracle. */
async function readColumnVisibility(page: Page) {
const text = await page.getByTestId('table-state').textContent()
const state = JSON.parse(text ?? '{}') as {
diff --git a/examples/svelte/composable-tables/src/components/PaginationControls.svelte b/examples/svelte/composable-tables/src/components/PaginationControls.svelte
index e56b58121f..f2cce0a518 100644
--- a/examples/svelte/composable-tables/src/components/PaginationControls.svelte
+++ b/examples/svelte/composable-tables/src/components/PaginationControls.svelte
@@ -7,6 +7,7 @@
} from '../hooks/table'
const table = useTableContext()
+ const pagination = $derived(table.atoms.pagination.get())
@@ -38,7 +39,7 @@
Page
- {(table.state.pagination.pageIndex + 1).toLocaleString()} of {table.getPageCount().toLocaleString()}
+ {(pagination.pageIndex + 1).toLocaleString()} of {table.getPageCount().toLocaleString()}
@@ -47,7 +48,7 @@
type="number"
min="1"
max={table.getPageCount()}
- value={table.state.pagination.pageIndex + 1}
+ value={pagination.pageIndex + 1}
onchange={(e) => {
const page = e.currentTarget.value ? Number(e.currentTarget.value) - 1 : 0
table.setPageIndex(page)
@@ -55,7 +56,7 @@
/>
{
table.setPageSize(Number(e.currentTarget.value))
}}
diff --git a/examples/svelte/composable-tables/src/components/ProductsTable.svelte b/examples/svelte/composable-tables/src/components/ProductsTable.svelte
index 6fa4976807..5d244e6d6b 100644
--- a/examples/svelte/composable-tables/src/components/ProductsTable.svelte
+++ b/examples/svelte/composable-tables/src/components/ProductsTable.svelte
@@ -67,18 +67,9 @@
enableRowSelection: true,
})
- // Reactive derived values from table state
- let sorting = $derived(table.state.sorting)
- let columnFilters = $derived(table.state.columnFilters)
-
- // IMPORTANT: Derive rows from table state so Svelte tracks the dependency.
- // We must read a $state value that changes on every table update.
- // JSON.stringify forces a deep read, ensuring Svelte sees the dependency.
- const rows = $derived.by(() => {
- data
- JSON.stringify(table.state)
- return table.getRowModel().rows
- })
+ const sorting = $derived(table.atoms.sorting.get())
+ const columnFilters = $derived(table.atoms.columnFilters.get())
+ const rows = $derived(table.getRowModel().rows)
diff --git a/examples/svelte/composable-tables/src/components/UsersTable.svelte b/examples/svelte/composable-tables/src/components/UsersTable.svelte
index 550349dffc..0e527a0ca8 100644
--- a/examples/svelte/composable-tables/src/components/UsersTable.svelte
+++ b/examples/svelte/composable-tables/src/components/UsersTable.svelte
@@ -77,20 +77,9 @@
debugTable: true,
})
- // Reactive derived values from table state.
- // Reading table.state creates a $state dependency (via the notifier)
- // that triggers re-renders when any table state changes.
- let sorting = $derived(table.state.sorting)
- let columnFilters = $derived(table.state.columnFilters)
-
- // IMPORTANT: Derive rows from table state so Svelte tracks the dependency.
- // We must read a $state value that changes on every table update.
- // JSON.stringify forces a deep read, ensuring Svelte sees the dependency.
- const rows = $derived.by(() => {
- data
- JSON.stringify(table.state)
- return table.getRowModel().rows
- })
+ const sorting = $derived(table.atoms.sorting.get())
+ const columnFilters = $derived(table.atoms.columnFilters.get())
+ const rows = $derived(table.getRowModel().rows)
diff --git a/examples/svelte/composable-tables/tests/e2e/smoke.spec.ts b/examples/svelte/composable-tables/tests/e2e/smoke.spec.ts
index 6188138993..1805a3e997 100644
--- a/examples/svelte/composable-tables/tests/e2e/smoke.spec.ts
+++ b/examples/svelte/composable-tables/tests/e2e/smoke.spec.ts
@@ -102,3 +102,38 @@ test('renders the table without crashing', async ({ page }) => {
await server.close()
}
})
+
+test('keeps createAppTable instances reactive and isolated', async ({
+ page,
+}) => {
+ const { errors, server } = await openExample(page)
+
+ try {
+ const containers = page.locator('.table-container')
+ const users = containers.filter({ hasText: 'Users Table' }).first()
+ const products = containers.filter({ hasText: 'Products Table' }).first()
+ const usersPage = users.locator('.pagination input[type="number"]')
+ const productsPage = products.locator('.pagination input[type="number"]')
+ const firstNameHeader = users
+ .locator('thead th')
+ .filter({ hasText: 'First Name' })
+ .first()
+
+ await expect(usersPage).toHaveValue('1')
+ await expect(productsPage).toHaveValue('1')
+
+ await firstNameHeader.click({ position: { x: 8, y: 8 } })
+ await expect(firstNameHeader.locator('.sort-indicator')).toHaveText('🔼')
+
+ await users
+ .locator('.pagination')
+ .getByRole('button', { name: '>', exact: true })
+ .click()
+
+ await expect(usersPage).toHaveValue('2')
+ await expect(productsPage).toHaveValue('1')
+ expect(errors).toEqual([])
+ } finally {
+ await server.close()
+ }
+})
diff --git a/examples/svelte/expanding/src/App.svelte b/examples/svelte/expanding/src/App.svelte
index f0639ed9b3..a08bc24fa5 100644
--- a/examples/svelte/expanding/src/App.svelte
+++ b/examples/svelte/expanding/src/App.svelte
@@ -118,14 +118,8 @@
// maxLeafRowFilterDepth: 0, // with filtering, only filter root rows
debugTable: true,
},
- (state) => ({
- expanded: state.expanded,
- pagination: state.pagination,
- rowSelection: state.rowSelection,
- columnFilters: state.columnFilters,
- sorting: state.sorting,
- }),
)
+ const pagination = $derived(table.atoms.pagination.get())