@tanstack/svelte-table 9.0.0-beta.38 → 9.0.0-beta.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,289 +0,0 @@
1
- ---
2
- name: svelte/compose-with-tanstack-virtual
3
- description: >
4
- `@tanstack/svelte-table` does not include virtualization — pair it with
5
- `@tanstack/svelte-virtual`. Use `createVirtualizer({ count, estimateSize, getScrollElement,
6
- ... })`, feed `table.getRowModel().rows.length` as `count`, render only
7
- `$rowVirtualizer.getVirtualItems()`, position rows with `transform: translateY(...)` and a
8
- container of `getTotalSize()`. Use `measureElement` actions for dynamic row heights. Svelte 5+
9
- only — `$state` for refs, `$effect` to sync count.
10
- type: composition
11
- library: tanstack-table
12
- framework: svelte
13
- library_version: '9.0.0-alpha.48'
14
- requires:
15
- - svelte/table-state
16
- - row-expanding
17
- sources:
18
- - TanStack/table:docs/guide/virtualization.md
19
- - TanStack/table:examples/svelte/virtualized-rows/
20
- - TanStack/table:examples/svelte/virtualized-columns/
21
- - TanStack/table:examples/svelte/virtualized-infinite-scrolling/
22
- ---
23
-
24
- # Compose with TanStack Virtual (Svelte)
25
-
26
- TanStack Table is **not** a virtualizer. For lists / grids past a few thousand rows (or with
27
- heavy per-row markup), pair it with `@tanstack/svelte-virtual`.
28
-
29
- ## Install
30
-
31
- ```bash
32
- pnpm add @tanstack/svelte-virtual
33
- ```
34
-
35
- ## Core mental model
36
-
37
- - TanStack Table gives you `rows: row[]` (already filtered / sorted / paged / grouped).
38
- - TanStack Virtual takes `count` (the length) and returns `virtualItems` (the slice currently
39
- in view).
40
- - You render only those virtual items, absolutely positioned, inside a container sized to
41
- `getTotalSize()` pixels.
42
-
43
- `createVirtualizer` returns a Svelte store. Read its current value with `$rowVirtualizer` or
44
- `get(rowVirtualizer)` (from `svelte/store`).
45
-
46
- ## Basic row virtualization
47
-
48
- ```svelte
49
- <script lang="ts">
50
- import {
51
- columnSizingFeature,
52
- createSortedRowModel,
53
- createTable,
54
- rowSortingFeature,
55
- sortFns,
56
- tableFeatures,
57
- FlexRender,
58
- } from '@tanstack/svelte-table'
59
- import { createVirtualizer } from '@tanstack/svelte-virtual'
60
- import { get } from 'svelte/store'
61
-
62
- const features = tableFeatures({
63
- columnSizingFeature,
64
- rowSortingFeature,
65
- sortedRowModel: createSortedRowModel(),
66
- sortFns,
67
- })
68
-
69
- let data = $state<Person[]>(makeData(200_000))
70
-
71
- const table = createTable({
72
- features,
73
- columns,
74
- get data() {
75
- return data
76
- },
77
- })
78
-
79
- let tableContainerRef = $state<HTMLDivElement | undefined>(undefined)
80
-
81
- const rows = $derived(table.getRowModel().rows)
82
-
83
- const rowVirtualizer = createVirtualizer({
84
- get count() {
85
- return rows.length
86
- },
87
- estimateSize: () => 33,
88
- getScrollElement: () => tableContainerRef ?? null,
89
- overscan: 5,
90
- })
91
-
92
- // svelte-virtual's store adapter does not reactively track getter options;
93
- // push updates explicitly when ref / count change.
94
- $effect(() => {
95
- if (tableContainerRef) {
96
- get(rowVirtualizer).setOptions({
97
- getScrollElement: () => tableContainerRef ?? null,
98
- })
99
- }
100
- })
101
-
102
- $effect(() => {
103
- get(rowVirtualizer).setOptions({ count: rows.length })
104
- })
105
- </script>
106
-
107
- <div
108
- bind:this={tableContainerRef}
109
- style="overflow: auto; position: relative; height: 800px;"
110
- >
111
- <table style="display: grid;">
112
- <thead style="display: grid; position: sticky; top: 0; z-index: 1;">
113
- {#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
114
- <tr style="display: flex; width: 100%;">
115
- {#each headerGroup.headers as header (header.id)}
116
- <th style="display: flex; width: {header.getSize()}px;">
117
- <FlexRender {header} />
118
- </th>
119
- {/each}
120
- </tr>
121
- {/each}
122
- </thead>
123
- <tbody
124
- style="display: grid; position: relative; height: {$rowVirtualizer.getTotalSize()}px;"
125
- >
126
- {#each $rowVirtualizer.getVirtualItems() as virtualRow (virtualRow.index)}
127
- {@const row = rows[virtualRow.index]}
128
- <tr
129
- data-index={virtualRow.index}
130
- style="display: flex; position: absolute; transform: translateY({virtualRow.start}px); width: 100%;"
131
- >
132
- {#each row.getAllCells() as cell (cell.id)}
133
- <td style="display: flex; width: {cell.column.getSize()}px;">
134
- <FlexRender {cell} />
135
- </td>
136
- {/each}
137
- </tr>
138
- {/each}
139
- </tbody>
140
- </table>
141
- </div>
142
- ```
143
-
144
- Why `display: grid` / `flex` instead of native table layout? Because the rows are absolutely
145
- positioned, the browser's native table layout algorithm can't size columns from non-flowing
146
- rows. CSS layout takes over.
147
-
148
- ## Dynamic row heights (`measureElement`)
149
-
150
- For variable row heights (multi-line cells, expanding rows), measure rendered nodes with the
151
- virtualizer's `measureElement` API.
152
-
153
- ```svelte
154
- <script lang="ts">
155
- const rowVirtualizer = createVirtualizer({
156
- get count() {
157
- return rows.length
158
- },
159
- estimateSize: () => 33,
160
- getScrollElement: () => tableContainerRef ?? null,
161
- measureElement:
162
- typeof window !== 'undefined' &&
163
- navigator.userAgent.indexOf('Firefox') === -1
164
- ? (element) => element.getBoundingClientRect().height
165
- : undefined,
166
- overscan: 5,
167
- })
168
-
169
- // Svelte action wrapping the virtualizer's measure call.
170
- function measureElement(node: HTMLTableRowElement) {
171
- get(rowVirtualizer).measureElement(node)
172
- }
173
- </script>
174
-
175
- <tr use:measureElement data-index={virtualRow.index} ...>...</tr>
176
- ```
177
-
178
- `data-index` is required — the virtualizer uses it to map a measured element back to its
179
- virtual item.
180
-
181
- > Firefox measures table-border rows incorrectly. The above guards against measuring there and
182
- > falls back to the estimate.
183
-
184
- ## Column virtualization
185
-
186
- `createVirtualizer` with `horizontal: true` against `table.getVisibleLeafColumns()`. Same
187
- pattern — only render `getVirtualItems()` cells per row, position with `translateX`.
188
-
189
- ```ts
190
- const columnVirtualizer = createVirtualizer({
191
- get count() {
192
- return visibleColumns.length
193
- },
194
- estimateSize: (index) => visibleColumns[index].getSize(),
195
- getScrollElement: () => tableContainerRef ?? null,
196
- horizontal: true,
197
- overscan: 3,
198
- })
199
- ```
200
-
201
- For combined row + column virtualization, render the row virtualizer's items, and inside each
202
- row render the column virtualizer's items. See `examples/svelte/virtualized-columns/`.
203
-
204
- ## Infinite scroll (load more on near-bottom)
205
-
206
- Subscribe to the virtualizer's `getVirtualItems()` and check the last one's index against your
207
- total available count.
208
-
209
- ```ts
210
- import { createInfiniteQuery } from '@tanstack/svelte-query'
211
-
212
- const infiniteQuery = createInfiniteQuery(() => ({
213
- queryKey: ['people-infinite'],
214
- queryFn: ({ pageParam }) => fetchPeople({ cursor: pageParam, pageSize: 50 }),
215
- initialPageParam: undefined as string | undefined,
216
- getNextPageParam: (last) => last.nextCursor,
217
- }))
218
-
219
- const flatData = $derived(
220
- infiniteQuery.data?.pages.flatMap((p) => p.rows) ?? [],
221
- )
222
-
223
- const table = createTable({
224
- features: tableFeatures({}),
225
- columns,
226
- get data() {
227
- return flatData
228
- },
229
- })
230
-
231
- const rows = $derived(table.getRowModel().rows)
232
-
233
- const rowVirtualizer = createVirtualizer({
234
- get count() {
235
- return rows.length
236
- },
237
- estimateSize: () => 33,
238
- getScrollElement: () => tableContainerRef ?? null,
239
- overscan: 10,
240
- })
241
-
242
- $effect(() => {
243
- const items = $rowVirtualizer.getVirtualItems()
244
- const last = items[items.length - 1]
245
- if (
246
- last &&
247
- last.index >= rows.length - 1 &&
248
- infiniteQuery.hasNextPage &&
249
- !infiniteQuery.isFetchingNextPage
250
- ) {
251
- infiniteQuery.fetchNextPage()
252
- }
253
- })
254
- ```
255
-
256
- ## Interaction with row expanding
257
-
258
- If `rowExpandingFeature` is registered, `table.getRowModel().rows` already flattens expanded
259
- sub-rows into a single sequential list. The virtualizer just sees a longer list — no special
260
- handling needed.
261
-
262
- For variable row heights driven by expand state, you'll want `measureElement` so the
263
- container resizes when a row expands.
264
-
265
- ## Pagination vs. virtualization
266
-
267
- Pick one. Virtualization is for "render all rows but render only the visible window".
268
- Pagination is for "the user navigates discrete pages". Combining them usually means you don't
269
- need either — drop pagination and let the virtualizer handle the rendering window.
270
-
271
- ## Common failure modes
272
-
273
- - **Forgot to push `count` updates.** `svelte-virtual` does not auto-track `get count()` —
274
- use `$effect` + `setOptions({ count })`.
275
- - **Native table layout with virtualized rows.** Columns collapse because absolutely
276
- positioned rows don't contribute to layout. Use `display: grid` / `flex`.
277
- - **No `data-index` on `<tr>`.** `measureElement` can't map back to virtual items.
278
- - **No `transform: translateY`.** Rows render at `top: 0` and stack visually.
279
- - **Missing container `height`.** No overflow, no scroll, no virtualization.
280
- - **Calling `get(rowVirtualizer).getVirtualItems()` in template.** Wrong access pattern;
281
- use `$rowVirtualizer.getVirtualItems()` (store auto-subscribe) or be sure to
282
- `import { get } from 'svelte/store'`.
283
- - **Reimplementing windowing manually.** Don't.
284
-
285
- ## Related skills
286
-
287
- - `tanstack-table/svelte/table-state` — `getRowModel()` and the reactivity model.
288
- - `tanstack-table/core/row-expanding` — flattening sub-rows for virtualization.
289
- - `tanstack-table/svelte/compose-with-tanstack-query` — infinite-scroll data source.
@@ -1,338 +0,0 @@
1
- ---
2
- name: svelte/getting-started
3
- description: >
4
- End-to-end first-table journey for `@tanstack/svelte-table@9` on Svelte 5. Install the adapter,
5
- declare `features` with `tableFeatures()` (including row-model factories and `*Fns` registries as
6
- feature slots), build a typed column helper with both `TFeatures` and `TData` generics, instantiate
7
- the table with `createTable(options)` using `$state` data and `get data()` reactive option getters,
8
- and render with `FlexRender`. Svelte 5+ only — Svelte 3/4 must use v8.
9
- type: lifecycle
10
- library: tanstack-table
11
- framework: svelte
12
- library_version: '9.0.0-alpha.48'
13
- requires:
14
- - setup
15
- - column-definitions
16
- - state-management
17
- - svelte/table-state
18
- sources:
19
- - TanStack/table:docs/installation.md
20
- - TanStack/table:docs/framework/svelte/svelte-table.md
21
- - TanStack/table:examples/svelte/basic-create-table/
22
- - TanStack/table:examples/svelte/basic-app-table/
23
- - TanStack/table:examples/svelte/basic-snippets/
24
- - TanStack/table:packages/svelte-table/src/index.ts
25
- ---
26
-
27
- # Getting Started — Svelte
28
-
29
- A first working `@tanstack/svelte-table` v9 table from a blank Svelte 5 project. Read this
30
- end-to-end before searching the docs — the v9 shape diverges enough from v8 (and from your
31
- muscle memory) that skimming will produce broken code.
32
-
33
- ## CRITICAL: Svelte version
34
-
35
- **`@tanstack/svelte-table@9` requires Svelte 5 or newer.** The adapter is built on runes
36
- (`$state`, `$derived.by`, `$effect.pre`). If your project is on Svelte 3 or 4, do **one** of:
37
-
38
- - Upgrade the project to Svelte 5, then install v9.
39
- - Stay on `@tanstack/svelte-table@8` for that project.
40
-
41
- There is no shim, no `/legacy` export, and no support path that runs v9 on Svelte 4.
42
-
43
- ## 1. Install
44
-
45
- ```bash
46
- pnpm add @tanstack/svelte-table
47
- # optional: external atoms / fine-grained selectors
48
- pnpm add @tanstack/svelte-store
49
- ```
50
-
51
- You do **not** install `@tanstack/table-core` separately — the Svelte adapter re-exports
52
- everything you need (column helpers, feature objects, row-model factories, types).
53
-
54
- ## 2. Define `features`
55
-
56
- v9 is explicit. You opt in to every feature and every row model. The core row model is
57
- included by default, so the minimum viable table is:
58
-
59
- ```ts
60
- const features = tableFeatures({})
61
- ```
62
-
63
- For anything beyond a flat table, register the features you'll use along with the matching
64
- row-model factories and `*Fns` registries — all as slots inside `tableFeatures`:
65
-
66
- ```ts
67
- import {
68
- columnFilteringFeature,
69
- createFilteredRowModel,
70
- createPaginatedRowModel,
71
- createSortedRowModel,
72
- filterFns,
73
- rowPaginationFeature,
74
- rowSortingFeature,
75
- sortFns,
76
- tableFeatures,
77
- } from '@tanstack/svelte-table'
78
-
79
- const features = tableFeatures({
80
- rowPaginationFeature,
81
- rowSortingFeature,
82
- columnFilteringFeature,
83
- paginatedRowModel: createPaginatedRowModel(),
84
- sortedRowModel: createSortedRowModel(),
85
- filteredRowModel: createFilteredRowModel(),
86
- sortFns,
87
- filterFns,
88
- })
89
- ```
90
-
91
- **Skipping a feature** in `features` means its state slice does not exist on `table.atoms`,
92
- its options on `createTable` do nothing, and its derived APIs (`table.setSorting`,
93
- `column.getCanSort`) are not on the instance.
94
-
95
- ## 3. Type your data and define columns
96
-
97
- ```ts
98
- type Person = {
99
- firstName: string
100
- lastName: string
101
- age: number
102
- visits: number
103
- status: 'relationship' | 'complicated' | 'single'
104
- progress: number
105
- }
106
- ```
107
-
108
- Both `ColumnDef` and the column helper take the two generics `<typeof features, TData>`:
109
-
110
- ```ts
111
- import { createColumnHelper, type ColumnDef } from '@tanstack/svelte-table'
112
-
113
- const columnHelper = createColumnHelper<typeof features, Person>()
114
-
115
- const columns = columnHelper.columns([
116
- columnHelper.accessor('firstName', {
117
- header: 'First Name',
118
- cell: (info) => info.getValue(),
119
- }),
120
- columnHelper.accessor((row) => row.lastName, {
121
- id: 'lastName',
122
- header: () => 'Last Name',
123
- cell: (info) => info.getValue(),
124
- }),
125
- columnHelper.accessor('age', { header: 'Age' }),
126
- columnHelper.accessor('visits', { header: 'Visits' }),
127
- columnHelper.accessor('status', { header: 'Status' }),
128
- columnHelper.accessor('progress', { header: 'Profile Progress' }),
129
- ])
130
- ```
131
-
132
- Or use raw `ColumnDef` arrays if you don't want the helper:
133
-
134
- ```ts
135
- const columns: Array<ColumnDef<typeof features, Person>> = [
136
- {
137
- accessorKey: 'firstName',
138
- header: 'First Name',
139
- cell: (info) => info.getValue(),
140
- },
141
- ]
142
- ```
143
-
144
- ## 4. Create the table
145
-
146
- Use Svelte 5 `$state` for the data and pass it through a **reactive getter** so the table
147
- re-evaluates `data` when the rune changes. The same pattern applies for any other reactive
148
- option (`columns`, `rowCount`, `state.*`).
149
-
150
- ```svelte
151
- <script lang="ts">
152
- import { createTable, tableFeatures } from '@tanstack/svelte-table'
153
- import { makeData, type Person } from './makeData'
154
-
155
- const features = tableFeatures({})
156
-
157
- // ... columns from step 3 ...
158
-
159
- let data = $state<Person[]>(makeData(20))
160
-
161
- const refreshData = () => {
162
- data = makeData(20)
163
- }
164
-
165
- const table = createTable({
166
- features,
167
- columns,
168
- get data() {
169
- return data
170
- },
171
- })
172
- </script>
173
- ```
174
-
175
- `createTable` syncs options inside `$effect.pre`, so external `$state` updates flow into the
176
- table **before** the DOM reads `getRowModel()` — no stale-frame bugs.
177
-
178
- ## 5. Render with `FlexRender`
179
-
180
- `FlexRender` handles plain strings, function renderers, component renderers
181
- (`renderComponent`), and snippet renderers (`renderSnippet`).
182
-
183
- ```svelte
184
- <script lang="ts">
185
- import { FlexRender } from '@tanstack/svelte-table'
186
- </script>
187
-
188
- <button onclick={refreshData}>Regenerate</button>
189
-
190
- <table>
191
- <thead>
192
- {#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
193
- <tr>
194
- {#each headerGroup.headers as header (header.id)}
195
- <th>
196
- {#if !header.isPlaceholder}
197
- <FlexRender {header} />
198
- {/if}
199
- </th>
200
- {/each}
201
- </tr>
202
- {/each}
203
- </thead>
204
- <tbody>
205
- {#each table.getRowModel().rows as row (row.id)}
206
- <tr>
207
- {#each row.getAllCells() as cell (cell.id)}
208
- <td><FlexRender {cell} /></td>
209
- {/each}
210
- </tr>
211
- {/each}
212
- </tbody>
213
- </table>
214
- ```
215
-
216
- **Key the `{#each}` blocks on stable ids.** Without keys, Svelte recreates nodes and loses
217
- focus, scroll, and any per-row component state.
218
-
219
- ## 6. Adding a feature — pagination
220
-
221
- ```svelte
222
- <script lang="ts">
223
- import {
224
- createPaginatedRowModel,
225
- createTable,
226
- rowPaginationFeature,
227
- tableFeatures,
228
- } from '@tanstack/svelte-table'
229
-
230
- const features = tableFeatures({
231
- rowPaginationFeature,
232
- paginatedRowModel: createPaginatedRowModel(),
233
- })
234
-
235
- const table = createTable({
236
- features,
237
- columns,
238
- get data() {
239
- return data
240
- },
241
- initialState: {
242
- pagination: { pageIndex: 0, pageSize: 10 },
243
- },
244
- })
245
- </script>
246
-
247
- <div>
248
- <button
249
- onclick={() => table.previousPage()}
250
- disabled={!table.getCanPreviousPage()}>Prev</button
251
- >
252
- <span
253
- >Page {table.atoms.pagination.get().pageIndex + 1} of {table.getPageCount()}</span
254
- >
255
- <button onclick={() => table.nextPage()} disabled={!table.getCanNextPage()}
256
- >Next</button
257
- >
258
- </div>
259
- ```
260
-
261
- To make pagination reactive in the controls, either pass a selector to `createTable` or use
262
- `subscribeTable`:
263
-
264
- ```ts
265
- import { subscribeTable } from '@tanstack/svelte-table'
266
-
267
- const pagination = subscribeTable(table.atoms.pagination)
268
- // pagination.current.pageIndex is reactive
269
- ```
270
-
271
- ## 7. `createTableHook` (when you have more than one table)
272
-
273
- For apps with multiple tables, define `features` (including row-model factories) and shared components once:
274
-
275
- ```ts
276
- // hooks/table.ts
277
- import {
278
- createPaginatedRowModel,
279
- createSortedRowModel,
280
- createTableHook,
281
- rowPaginationFeature,
282
- rowSortingFeature,
283
- sortFns,
284
- tableFeatures,
285
- } from '@tanstack/svelte-table'
286
-
287
- export const { createAppTable, createAppColumnHelper } = createTableHook({
288
- features: tableFeatures({
289
- rowPaginationFeature,
290
- rowSortingFeature,
291
- paginatedRowModel: createPaginatedRowModel(),
292
- sortedRowModel: createSortedRowModel(),
293
- sortFns,
294
- }),
295
- })
296
- ```
297
-
298
- ```svelte
299
- <script lang="ts">
300
- import { createAppColumnHelper, createAppTable } from './hooks/table'
301
-
302
- const columnHelper = createAppColumnHelper<Person>()
303
- const columns = columnHelper.columns([
304
- columnHelper.accessor('firstName', { header: 'First' }),
305
- ])
306
-
307
- const table = createAppTable({
308
- columns,
309
- get data() {
310
- return data
311
- },
312
- })
313
- </script>
314
- ```
315
-
316
- ## Common failure modes
317
-
318
- - **Svelte 3/4.** Adapter will not work. See top of file.
319
- - **`createSvelteTable` / `useSvelteTable` / `getCoreRowModel`** — all v8 names. v9 uses
320
- `createTable` with row-model factories registered as slots in `tableFeatures({ paginatedRowModel: createPaginatedRowModel(), ... })`.
321
- - **Plain `data` instead of `get data()` getter.** Table will not see data updates. Always
322
- pass a reactive getter for state that lives in `$state`.
323
- - **Missing feature in `features`.** `table.setSorting` / `column.getCanSort` won't exist.
324
- TypeScript will tell you; if it doesn't, you're missing the `<typeof features, TData>`
325
- generics on the column helper or `ColumnDef`.
326
- - **Plain object instead of `tableFeatures({...})`.** Loses typed atom keys; you'll get
327
- `unknown` state shapes everywhere.
328
- - **Unkeyed `{#each}` blocks.** Reuse bugs (focus jumps, wrong row selected).
329
- - **Reimplementing built-ins.** If you write a manual sort comparator across rows, you're
330
- re-doing `rowSortingFeature`. Register it instead.
331
-
332
- ## Next steps
333
-
334
- - `tanstack-table/svelte/table-state` — reactivity model, selectors, subscribeTable, ownership.
335
- - `tanstack-table/core/filtering` / `pagination` / `sorting` / `row-selection` — feature-by-feature.
336
- - `tanstack-table/svelte/compose-with-tanstack-query` — server-side data.
337
- - `tanstack-table/svelte/compose-with-tanstack-virtual` — large datasets.
338
- - `tanstack-table/svelte/production-readiness` — selector tuning, bundle size.