Docs
CodeRabbit
Cloudflare
Railway
SerpAPI
Clerk
OpenRouter
Netlify
WorkOS
AG Grid
Electric
Sentry
Unkey
Prisma
CodeRabbit
Cloudflare
Railway
SerpAPI
Clerk
OpenRouter
Netlify
WorkOS
AG Grid
Electric
Sentry
Unkey
Prisma
Table API Reference
Column API Reference
Row API Reference
Cell API Reference
Header API Reference
Features API Reference
Static Functions API Reference
Core Guides

Composable Tables Guide

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.

Examples

Setup

The composable tables example keeps the shared configuration in src/hooks/table.ts.

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,
  },
})

Returned Helpers

HelperPurpose
createAppTableCreates a Svelte table with shared features, row models, defaults, and registered components.
createAppColumnHelperCreates column helpers with TFeatures and registered component types already bound.
useTableContextReads the current table inside registered table components.
useCellContextReads the current cell inside registered cell components.
useHeaderContextReads the current header/footer inside registered header components.

Columns

Create one column helper per row type. The Svelte example uses renderComponent(...) when a column def returns a registered Svelte component.

svelte
<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>

Table Rendering

Create each table with createAppTable. In Svelte 5, pass reactive data through a getter so table options read the current rune value.

svelte
<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.

svelte
<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>

Reusing The Hook

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.