This skill builds on @tanstack/table-core#core and getting-started. Read them first for table ownership and Svelte construction.
State Mental Model
TanStack Table is primarily a state coordinator. Keep state internal unless another system must read, persist, or drive it. Without initialState, atoms, state, or on[State]Change, the table owns all registered slices.
table.baseAtomsare internal writable atoms initialized from resolved initial state.table.atomsare readonly derived atoms for the active owner of each registered slice.table.storeis the readonly flat store assembled from those atoms.
The Svelte adapter bridges TanStack Store dependency tracking into Svelte runes. table.atoms.<slice>.get(), table.store.get(), and table APIs become reactive when called in a template, $derived, $derived.by, or $effect. Outside those contexts they return current snapshots.
Only registered features create state and types. If pagination is missing, register rowPaginationFeature; do not add a cast or ad hoc state field. Keep features and columns stable and pass changing data through a getter.
Setup
Keep state internal unless another subsystem needs to own it. Read only the state slices a component needs.
<script lang="ts">
import {
createTable,
rowPaginationFeature,
tableFeatures,
} from '@tanstack/svelte-table'
const features = tableFeatures({ rowPaginationFeature })
const columns = [{ accessorKey: 'name' }]
let data = $state([{ name: 'Ada' }])
const table = createTable({
features,
columns,
get data() {
return data
},
})
const pagination = $derived(table.atoms.pagination.get())
</script>
<button => table.nextPage()} disabled={!table.getCanNextPage()}>
Page {pagination.pageIndex + 1}
</button>
Core Patterns
Read narrow or complete state
const pagination = $derived(table.atoms.pagination.get())
const pageIndex = $derived(table.atoms.pagination.get().pageIndex)
const rows = $derived(table.getRowModel().rows)
const stateJson = $derived(JSON.stringify(table.store.get(), null, 2))
Use atom reads for normal UI. A table.store.get() read intentionally re-runs for any registered state change, so reserve it for debug output, persistence, or computations that need the whole state.
Control a slice with value-or-updater semantics
import type { PaginationState, Updater } from '@tanstack/svelte-table'
let pagination = $state<PaginationState>({ pageIndex: 0, pageSize: 20 })
const updatePagination = (next: Updater<PaginationState>) => {
pagination = typeof next === 'function' ? next(pagination) : next
}
Pass a getter-backed state.pagination and onPaginationChange: updatePagination to createTable.
Reduce boilerplate with createTableState
import {
createTable,
createTableState,
rowPaginationFeature,
tableFeatures,
type PaginationState,
} from '@tanstack/svelte-table'
const features = tableFeatures({ rowPaginationFeature })
const columns = [{ accessorKey: 'name' }]
const data = [{ name: 'Ada' }]
const [pagination, setPagination] = createTableState<PaginationState>({
pageIndex: 0,
pageSize: 20,
})
const table = createTable({
features,
columns,
data,
state: {
get pagination() {
return pagination()
},
},
onPaginationChange: setPagination,
})
For better or worse, this resembles a small React useState hook: pagination() reads the current rune-backed value, while setPagination accepts either a value or a functional updater and can be passed directly to onPaginationChange.
Choose State Ownership
Use one owner per slice:
- Prefer internal state plus feature APIs for table-local interaction.
- Use
initialStatefor starting/reset values; changing it later does not reset state. - Use Svelte
$state, a getter-backedstateentry, and the matching callback for normal Svelte-owned controlled state. - Use a stable external atom in
atomsfor state shared as a raw TanStack Store atom. Do not also add its change callback.
External atoms win over controlled state, which syncs into the internal base atom. Avoid multiple owners. The global v8 onStateChange option is gone; subscribe to table.store if every state change must be observed imperatively.
When code outside the table consumes a raw external atom, use useSelector from @tanstack/svelte-store. Inside table-driven UI, read the rune-aware table.atoms.<slice>.get() wrapper.
Initialize, Update, and Reset
Prefer setSorting, nextPage, toggleVisibility, toggleSelected, and other feature APIs over direct state writes. Write a base atom only for rare internal-state needs; write the external atom when atoms.<slice> owns it.
table.resetSorting()
table.resetPagination()
table.resetPagination(true)
Feature resets use table.initialState unless true requests the feature default and can flow to external owners. Core table.reset() resets internal base atoms only. Use feature types such as PaginationState for a slice and TableState<typeof features> for the complete registered state.
Common Mistakes
HIGH Keeping removed adapter selectors
Wrong:
const table = createTable(options, (state) => state.pagination)
const pageIndex = table.state.pageIndex
Correct:
const table = createTable(options)
const pagination = $derived(table.atoms.pagination.get())
In v9, createTable and createAppTable take only options, table.state is absent, and subscribeTable and SubscribeSource are no longer exported. Use native tracked Svelte reads and $derived projections.
Source: docs/framework/svelte/guide/migrating.md
HIGH Controlling without writing back
Wrong:
const options = { state: { pagination }, onPaginationChange: console.log }
Correct:
const options = {
state: {
get pagination() {
return pagination
},
},
onPaginationChange: updatePagination,
}
A controlled slice is frozen unless every updater is resolved into the owning rune.
Source: docs/framework/svelte/guide/table-state.md
HIGH Snapshotting outside tracking
Wrong:
const pageIndex = table.store.get().pagination.pageIndex
Correct inside a component:
const pageIndex = $derived(table.atoms.pagination.get().pageIndex)
The first line is only a current snapshot when it runs outside a template or rune. The second line is a narrow native Svelte derivation.
Source: packages/svelte-table/src/createTable.svelte.ts
MEDIUM Declaring one slice in two owners
Wrong:
const options = { initialState: { pagination: start }, state: { pagination } }
Correct:
const options = {
state: {
get pagination() {
return pagination
},
},
}
Controlled atoms or state wins over initialState; choose one owner per slice.
Source: docs/framework/svelte/guide/table-state.md
MEDIUM Fighting automatic page reset
Wrong:
table.setPageIndex(4)
data = filteredData
Correct:
const options = { autoResetPageIndex: false }
Client row-model changes reset the page by default; disable it only when the application handles invalid empty pages.
Source: docs/framework/svelte/guide/pagination.md
API Discovery
Inspect node_modules/@tanstack/svelte-table/dist/createTable.svelte.d.ts, createTableHook.svelte.d.ts, and createTableState.svelte.d.ts; inspect registered state slices in the matching core feature source.