Composable tables are app-level table factories built with createTableHook. They let Svelte apps define shared features, row models, default options, and reusable Svelte components once, then create multiple tables from that setup.
Use this pattern when several tables should share behavior and rendering conventions. For one standalone table, createTable is usually enough.
The composable tables example keeps the shared configuration in src/hooks/table.ts.
import {
columnFilteringFeature,
createFilteredRowModel,
createPaginatedRowModel,
createSortedRowModel,
createTableHook,
filterFns,
rowPaginationFeature,
rowSortingFeature,
sortFns,
tableFeatures,
} from '@tanstack/svelte-table'
import PaginationControls from '../components/PaginationControls.svelte'
import RowCount from '../components/RowCount.svelte'
import TableToolbar from '../components/TableToolbar.svelte'
import CategoryCell from '../components/CategoryCell.svelte'
import NumberCell from '../components/NumberCell.svelte'
import PriceCell from '../components/PriceCell.svelte'
import ProgressCell from '../components/ProgressCell.svelte'
import RowActionsCell from '../components/RowActionsCell.svelte'
import StatusCell from '../components/StatusCell.svelte'
import TextCell from '../components/TextCell.svelte'
import ColumnFilter from '../components/ColumnFilter.svelte'
import FooterColumnId from '../components/FooterColumnId.svelte'
import FooterSum from '../components/FooterSum.svelte'
import SortIndicator from '../components/SortIndicator.svelte'
const features = tableFeatures({
columnFilteringFeature,
rowPaginationFeature,
rowSortingFeature,
sortedRowModel: createSortedRowModel(),
filteredRowModel: createFilteredRowModel(),
paginatedRowModel: createPaginatedRowModel(),
sortFns,
filterFns,
})
export const {
createAppColumnHelper,
createAppTable,
useTableContext,
useCellContext,
useHeaderContext,
} = createTableHook({
features,
getRowId: (row) => row.id,
tableComponents: {
PaginationControls,
RowCount,
TableToolbar,
},
cellComponents: {
TextCell,
NumberCell,
StatusCell,
ProgressCell,
RowActionsCell,
PriceCell,
CategoryCell,
},
headerComponents: {
SortIndicator,
ColumnFilter,
FooterColumnId,
FooterSum,
},
})import {
columnFilteringFeature,
createFilteredRowModel,
createPaginatedRowModel,
createSortedRowModel,
createTableHook,
filterFns,
rowPaginationFeature,
rowSortingFeature,
sortFns,
tableFeatures,
} from '@tanstack/svelte-table'
import PaginationControls from '../components/PaginationControls.svelte'
import RowCount from '../components/RowCount.svelte'
import TableToolbar from '../components/TableToolbar.svelte'
import CategoryCell from '../components/CategoryCell.svelte'
import NumberCell from '../components/NumberCell.svelte'
import PriceCell from '../components/PriceCell.svelte'
import ProgressCell from '../components/ProgressCell.svelte'
import RowActionsCell from '../components/RowActionsCell.svelte'
import StatusCell from '../components/StatusCell.svelte'
import TextCell from '../components/TextCell.svelte'
import ColumnFilter from '../components/ColumnFilter.svelte'
import FooterColumnId from '../components/FooterColumnId.svelte'
import FooterSum from '../components/FooterSum.svelte'
import SortIndicator from '../components/SortIndicator.svelte'
const features = tableFeatures({
columnFilteringFeature,
rowPaginationFeature,
rowSortingFeature,
sortedRowModel: createSortedRowModel(),
filteredRowModel: createFilteredRowModel(),
paginatedRowModel: createPaginatedRowModel(),
sortFns,
filterFns,
})
export const {
createAppColumnHelper,
createAppTable,
useTableContext,
useCellContext,
useHeaderContext,
} = createTableHook({
features,
getRowId: (row) => row.id,
tableComponents: {
PaginationControls,
RowCount,
TableToolbar,
},
cellComponents: {
TextCell,
NumberCell,
StatusCell,
ProgressCell,
RowActionsCell,
PriceCell,
CategoryCell,
},
headerComponents: {
SortIndicator,
ColumnFilter,
FooterColumnId,
FooterSum,
},
})| Helper | Purpose |
|---|---|
| createAppTable | Creates a Svelte table with shared features, row models, defaults, and registered components. |
| createAppColumnHelper | Creates column helpers with TFeatures and registered component types already bound. |
| useTableContext | Reads the current table inside registered table components. |
| useCellContext | Reads the current cell inside registered cell components. |
| useHeaderContext | Reads the current header/footer inside registered header components. |
Create one column helper per row type. The Svelte example uses renderComponent(...) when a column def returns a registered Svelte component.
<script lang="ts">
import { renderComponent } from '@tanstack/svelte-table'
import { createAppColumnHelper } from '../hooks/table'
import type { Person } from '../makeData'
const personColumnHelper = createAppColumnHelper<Person>()
const columns = personColumnHelper.columns([
personColumnHelper.accessor('firstName', {
header: 'First Name',
footer: (props) => props.column.id,
cell: ({ cell }) => renderComponent(cell.TextCell),
}),
personColumnHelper.accessor('age', {
header: 'Age',
footer: (props) => props.column.id,
cell: ({ cell }) => renderComponent(cell.NumberCell),
}),
])
</script><script lang="ts">
import { renderComponent } from '@tanstack/svelte-table'
import { createAppColumnHelper } from '../hooks/table'
import type { Person } from '../makeData'
const personColumnHelper = createAppColumnHelper<Person>()
const columns = personColumnHelper.columns([
personColumnHelper.accessor('firstName', {
header: 'First Name',
footer: (props) => props.column.id,
cell: ({ cell }) => renderComponent(cell.TextCell),
}),
personColumnHelper.accessor('age', {
header: 'Age',
footer: (props) => props.column.id,
cell: ({ cell }) => renderComponent(cell.NumberCell),
}),
])
</script>Create each table with createAppTable. In Svelte 5, pass reactive data through a getter so table options read the current rune value.
<script lang="ts">
let data = $state(makeData(1_000))
const table = createAppTable({
columns,
get data() {
return data
},
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
})
</script><script lang="ts">
let data = $state(makeData(1_000))
const table = createAppTable({
columns,
get data() {
return data
},
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
})
</script>The returned table includes Svelte components for AppTable, AppHeader, AppCell, and AppFooter, plus the registered table components.
<table.AppTable>
<div class="table-container">
<table.TableToolbar title="Users Table" onRefresh={refreshData} />
<table>
<thead>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<tr>
{#each headerGroup.headers as h (h.id)}
<table.AppHeader header={h}>
{#snippet children(header)}
<th onclick={header.column.getToggleSortingHandler()}>
<header.FlexRender header={header} />
<header.SortIndicator />
<header.ColumnFilter />
</th>
{/snippet}
</table.AppHeader>
{/each}
</tr>
{/each}
</thead>
<tbody>
{#each rows as row (row.id)}
<tr>
{#each row.getAllCells() as cell (cell.id)}
<table.AppCell cell={cell}>
{#snippet children(appCell)}
<td>
<appCell.FlexRender cell={appCell} />
</td>
{/snippet}
</table.AppCell>
{/each}
</tr>
{/each}
</tbody>
</table>
<table.PaginationControls />
<table.RowCount />
</div>
</table.AppTable><table.AppTable>
<div class="table-container">
<table.TableToolbar title="Users Table" onRefresh={refreshData} />
<table>
<thead>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<tr>
{#each headerGroup.headers as h (h.id)}
<table.AppHeader header={h}>
{#snippet children(header)}
<th onclick={header.column.getToggleSortingHandler()}>
<header.FlexRender header={header} />
<header.SortIndicator />
<header.ColumnFilter />
</th>
{/snippet}
</table.AppHeader>
{/each}
</tr>
{/each}
</thead>
<tbody>
{#each rows as row (row.id)}
<tr>
{#each row.getAllCells() as cell (cell.id)}
<table.AppCell cell={cell}>
{#snippet children(appCell)}
<td>
<appCell.FlexRender cell={appCell} />
</td>
{/snippet}
</table.AppCell>
{/each}
</tr>
{/each}
</tbody>
</table>
<table.PaginationControls />
<table.RowCount />
</div>
</table.AppTable>The Users and Products Svelte components import the same createAppColumnHelper and createAppTable from src/hooks/table.ts. Each component owns its $state data and columns, while the shared hook owns features, row models, row IDs, and the component registry.