TanStack
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. The @tanstack/table-core package gives you the framework-agnostic core directly, so you provide the rendering and subscribe to table state changes yourself. This page gets you from install to a rendering vanilla table, then shows how to layer on your first feature.

Installation

shell
npm install @tanstack/table-core

Your First Table

The TypeScript module below is complete. Use it with an HTML page that contains <div id="app"></div> and you will see a working table.

ts
import {
  constructTable,
  tableFeatures,
  type ColumnDef,
} from '@tanstack/table-core'
import { FlexRender } from '@tanstack/table-core/flex-render'
import { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings'

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

// 2. Give your data a stable reference
const data: 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
const features = tableFeatures({
  coreReactivityFeature: storeReactivityBindings(),
})

// 4. Define your columns. Renderers return values or HTML strings.
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: () => '<span>Last Name</span>',
    cell: (info) => `<i>${info.getValue<string>()}</i>`,
  },
  {
    accessorKey: 'age',
    header: () => 'Age',
  },
]

// 5. Create the table instance
const table = constructTable({
  features,
  columns,
  data,
})

const app = document.getElementById('app')

if (!app) {
  throw new Error('Missing #app element')
}

// 6. Render markup from the table instance APIs
const renderTable = () => {
  const tableElement = document.createElement('table')
  const thead = document.createElement('thead')
  const tbody = document.createElement('tbody')

  table.getHeaderGroups().forEach((headerGroup) => {
    const tr = document.createElement('tr')

    headerGroup.headers.forEach((header) => {
      const th = document.createElement('th')
      th.innerHTML = header.isPlaceholder
        ? ''
        : String(FlexRender({ header }) ?? '')
      tr.appendChild(th)
    })

    thead.appendChild(tr)
  })

  table.getRowModel().rows.forEach((row) => {
    const tr = document.createElement('tr')

    row.getAllCells().forEach((cell) => {
      const td = document.createElement('td')
      td.innerHTML = String(FlexRender({ cell }) ?? '')
      tr.appendChild(td)
    })

    tbody.appendChild(tr)
  })

  tableElement.appendChild(thead)
  tableElement.appendChild(tbody)

  app.replaceChildren(tableElement)
}

table.store.subscribe(() => renderTable())
renderTable()

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 the tableFeatures({...}) call when you need them.
  • Vanilla usage must provide coreReactivityFeature: storeReactivityBindings() because there is no framework adapter to wire the core TanStack Store atoms into a rendering system for you.
  • FlexRender renders the header, cell, and footer definitions from your columns. In vanilla usage, renderers commonly return strings or HTML strings that you place into the DOM.
  • Subscribe to table.store when your UI should redraw after table state changes.

See the full Basic 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 the sortedRowModel factory in tableFeatures, then wire the header click handler.

ts
import {
  constructTable,
  createSortedRowModel,
  rowSortingFeature,
  sortFns,
  tableFeatures,
} from '@tanstack/table-core'
import { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings'

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

// columns and data unchanged from above

const table = constructTable({
  features,
  columns,
  data,
})
ts
// In renderTable, replace the header rendering with sortable header markup.
table.getHeaderGroups().forEach((headerGroup) => {
  const tr = document.createElement('tr')

  headerGroup.headers.forEach((header) => {
    const th = document.createElement('th')
    const button = document.createElement('button')

    button.type = 'button'
    button.disabled = !header.column.getCanSort()
    button.innerHTML = header.isPlaceholder
      ? ''
      : String(FlexRender({ header }) ?? '')
    button.innerHTML +=
      {
        asc: ' 🔼',
        desc: ' 🔽',
      }[header.column.getIsSorted() as string] ?? ''
    button.addEventListener('click', (event) => {
      header.column.getToggleSortingHandler()?.(event)
    })

    th.appendChild(button)
    tr.appendChild(th)
  })

  thead.appendChild(tr)
})

Clicking a header now toggles between ascending, descending, and unsorted. Every other feature follows this same pattern: register the feature and its row model factory (if it has one) in tableFeatures, then use the APIs it adds to the table, columns, and rows. See 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. When you use @tanstack/table-core directly, you decide when to read state, subscribe to state, and redraw your UI. 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 vanilla usage.

Feature examples. Browse runnable vanilla examples such as Pagination and Sorting to see complete DOM rendering setups.

Core guides. The framework-agnostic guides cover the main table objects: Data, Column Definitions, Table Instance, Rows, Cells, and Header Groups.