Docs
Cloudflare
Railway
CodeRabbit
AG Grid
WorkOS
Netlify
Clerk
OpenRouter
SerpAPI
Electric
Sentry
Unkey
Prisma
Cloudflare
Railway
CodeRabbit
AG Grid
WorkOS
Netlify
Clerk
OpenRouter
SerpAPI
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
Getting Started

Quick Start

TanStack Table is a headless table library. It manages your table's state and logic (sorting, filtering, pagination, selection, and more) while you keep 100% control over the markup and styles. This page gets you from install to a rendering Svelte table, then shows how to layer on your first feature.

IMPORTANT: This version of @tanstack/svelte-table only supports Svelte 5 or newer. For Svelte 3/4 support, use version 8 of @tanstack/svelte-table.

Installation

TanStack Table v9 is currently published under the beta tag:

sh
npm install @tanstack/svelte-table@beta
npm install @tanstack/svelte-table@beta

Your First Table

The component below is complete. Paste it into a Svelte 5 app and you will see a working table.

svelte
<script lang="ts">
  import { createTable, FlexRender, tableFeatures } from '@tanstack/svelte-table'
  import type { ColumnDef } from '@tanstack/svelte-table'

  // 1. Define the shape of your data
  type Person = {
    firstName: string
    lastName: string
    age: number
  }

  // 2. Store data with a $state rune for reactivity
  let data = $state<Array<Person>>([
    { firstName: 'tanner', lastName: 'linsley', age: 24 },
    { firstName: 'tandy', lastName: 'miller', age: 40 },
    { firstName: 'joe', lastName: 'dirte', age: 45 },
  ])

  // 3. New in v9: declare which features this table uses (none yet)
  const features = tableFeatures({})

  // 4. Define your columns
  const columns: Array<ColumnDef<typeof features, Person>> = [
    {
      accessorKey: 'firstName', // accessorKey shorthand
      header: 'First Name',
      cell: (info) => info.getValue(),
    },
    {
      accessorFn: (row) => row.lastName, // accessorFn alternative with a custom id
      id: 'lastName',
      header: () => 'Last Name',
      cell: (info) => info.getValue(),
    },
    {
      accessorKey: 'age',
      header: () => 'Age',
    },
  ]

  // 5. Create the table instance
  const table = createTable({
    features,
    columns,
    get data() {
      return data // a getter keeps the table in sync with the $state rune
    },
  })
</script>

<!-- 6. Render markup from the table instance APIs -->
<table>
  <thead>
    {#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
      <tr>
        {#each headerGroup.headers as header (header.id)}
          <th>
            {#if !header.isPlaceholder}
              <FlexRender header={header} />
            {/if}
          </th>
        {/each}
      </tr>
    {/each}
  </thead>
  <tbody>
    {#each table.getRowModel().rows as row (row.id)}
      <tr>
        {#each row.getAllCells() as cell (cell.id)}
          <td>
            <FlexRender cell={cell} />
          </td>
        {/each}
      </tr>
    {/each}
  </tbody>
</table>
<script lang="ts">
  import { createTable, FlexRender, tableFeatures } from '@tanstack/svelte-table'
  import type { ColumnDef } from '@tanstack/svelte-table'

  // 1. Define the shape of your data
  type Person = {
    firstName: string
    lastName: string
    age: number
  }

  // 2. Store data with a $state rune for reactivity
  let data = $state<Array<Person>>([
    { firstName: 'tanner', lastName: 'linsley', age: 24 },
    { firstName: 'tandy', lastName: 'miller', age: 40 },
    { firstName: 'joe', lastName: 'dirte', age: 45 },
  ])

  // 3. New in v9: declare which features this table uses (none yet)
  const features = tableFeatures({})

  // 4. Define your columns
  const columns: Array<ColumnDef<typeof features, Person>> = [
    {
      accessorKey: 'firstName', // accessorKey shorthand
      header: 'First Name',
      cell: (info) => info.getValue(),
    },
    {
      accessorFn: (row) => row.lastName, // accessorFn alternative with a custom id
      id: 'lastName',
      header: () => 'Last Name',
      cell: (info) => info.getValue(),
    },
    {
      accessorKey: 'age',
      header: () => 'Age',
    },
  ]

  // 5. Create the table instance
  const table = createTable({
    features,
    columns,
    get data() {
      return data // a getter keeps the table in sync with the $state rune
    },
  })
</script>

<!-- 6. Render markup from the table instance APIs -->
<table>
  <thead>
    {#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
      <tr>
        {#each headerGroup.headers as header (header.id)}
          <th>
            {#if !header.isPlaceholder}
              <FlexRender header={header} />
            {/if}
          </th>
        {/each}
      </tr>
    {/each}
  </thead>
  <tbody>
    {#each table.getRowModel().rows as row (row.id)}
      <tr>
        {#each row.getAllCells() as cell (cell.id)}
          <td>
            <FlexRender cell={cell} />
          </td>
        {/each}
      </tr>
    {/each}
  </tbody>
</table>

A few things to note:

  • tableFeatures({}) declares which optional features the table uses. Registering only what you need keeps bundles small and gives TypeScript accurate types for the table instance.
  • The core row model is always included automatically. Feature row models (sorting, filtering, pagination) are registered as slots directly on tableFeatures({...}) when you need them.
  • Passing data through a getter (get data() { return data }) lets the table track the $state rune, so reassigning data updates the table.
  • The FlexRender component renders the header, cell, and footer definitions from your columns. It handles plain values, Svelte components wrapped with renderComponent, and snippets wrapped with renderSnippet (see the Basic Snippets example).

See the full Basic createTable example for a runnable version with more columns and a footer.

Add a Feature: Sorting

Features are opt-in in v9. To make columns sortable, register rowSortingFeature and sortedRowModel in tableFeatures, and wire a click handler into the header markup.

svelte
<script lang="ts">
  import {
    createSortedRowModel,
    createTable,
    FlexRender,
    rowSortingFeature,
    sortFns,
    tableFeatures,
  } from '@tanstack/svelte-table'

  const features = tableFeatures({
    rowSortingFeature, // enables sorting APIs and state
    sortedRowModel: createSortedRowModel(), // client-side sorting
    sortFns,
  })

  const table = createTable(
    {
      features,
      columns,
      get data() {
        return data
      },
    },
    // an optional second argument selects which state to track; it defaults
    // to the full registered state, so it is omitted here
  )
</script>

<table>
  <thead>
    {#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
      <tr>
        {#each headerGroup.headers as header (header.id)}
          <th>
            {#if !header.isPlaceholder}
              <button
                disabled={!header.column.getCanSort()}
                onclick={header.column.getToggleSortingHandler()}
              >
                <FlexRender header={header} />
                {#if header.column.getIsSorted() === 'asc'}
                  🔼
                {:else if header.column.getIsSorted() === 'desc'}
                  🔽
                {/if}
              </button>
            {/if}
          </th>
        {/each}
      </tr>
    {/each}
  </thead>
  <!-- tbody unchanged from above -->
</table>
<script lang="ts">
  import {
    createSortedRowModel,
    createTable,
    FlexRender,
    rowSortingFeature,
    sortFns,
    tableFeatures,
  } from '@tanstack/svelte-table'

  const features = tableFeatures({
    rowSortingFeature, // enables sorting APIs and state
    sortedRowModel: createSortedRowModel(), // client-side sorting
    sortFns,
  })

  const table = createTable(
    {
      features,
      columns,
      get data() {
        return data
      },
    },
    // an optional second argument selects which state to track; it defaults
    // to the full registered state, so it is omitted here
  )
</script>

<table>
  <thead>
    {#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
      <tr>
        {#each headerGroup.headers as header (header.id)}
          <th>
            {#if !header.isPlaceholder}
              <button
                disabled={!header.column.getCanSort()}
                onclick={header.column.getToggleSortingHandler()}
              >
                <FlexRender header={header} />
                {#if header.column.getIsSorted() === 'asc'}
                  🔼
                {:else if header.column.getIsSorted() === 'desc'}
                  🔽
                {/if}
              </button>
            {/if}
          </th>
        {/each}
      </tr>
    {/each}
  </thead>
  <!-- tbody unchanged from above -->
</table>

Clicking a header now toggles between ascending, descending, and unsorted. Every other feature follows this same pattern: register the feature and its row model together in tableFeatures({...}), then use the APIs it adds to the table, columns, and rows. See the Sorting Guide and the Sorting example for custom sort functions, multi-sorting, and per-column options.

Where to Go Next

Table state. In v9, table state is backed by TanStack Store atoms, and the Svelte adapter installs rune-based reactivity for you. You usually do not need to manage state yourself: set initialState for starting values and call feature APIs like table.setSorting(...) or table.nextPage(). When your app should own a state slice, or you want fine-grained subscriptions, read the Table State Guide. It is the foundational guide for everything else.

Feature guides. Each feature has its own guide, such as Column Filtering, Pagination, Row Selection, and Column Visibility.

Composable tables. When multiple tables in your app share features, row models, and component conventions, define them once with createTableHook:

ts
const features = tableFeatures({
  rowSortingFeature,
  sortedRowModel: createSortedRowModel(),
  sortFns,
})

const { createAppTable, createAppColumnHelper } = createTableHook({ features })
const features = tableFeatures({
  rowSortingFeature,
  sortedRowModel: createSortedRowModel(),
  sortFns,
})

const { createAppTable, createAppColumnHelper } = createTableHook({ features })

See the Composable Tables Guide for the full pattern, including pre-bound cell and header components.

Examples. Browse the runnable Svelte examples, from basic tables to feature demos, to see intended usage end to end.