|
| 1 | +--- |
| 2 | +title: Quick Start |
| 3 | +--- |
| 4 | + |
| 5 | +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. |
| 6 | + |
| 7 | +## Installation |
| 8 | + |
| 9 | +TanStack Table v9 is currently published under the `beta` tag: |
| 10 | + |
| 11 | +```bash |
| 12 | +npm install @tanstack/table-core@beta |
| 13 | +``` |
| 14 | + |
| 15 | +## Your First Table |
| 16 | + |
| 17 | +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. |
| 18 | + |
| 19 | +```ts |
| 20 | +import { |
| 21 | + constructTable, |
| 22 | + tableFeatures, |
| 23 | + type ColumnDef, |
| 24 | +} from '@tanstack/table-core' |
| 25 | +import { FlexRender } from '@tanstack/table-core/flex-render' |
| 26 | +import { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings' |
| 27 | + |
| 28 | +// 1. Define the shape of your data |
| 29 | +type Person = { |
| 30 | + firstName: string |
| 31 | + lastName: string |
| 32 | + age: number |
| 33 | +} |
| 34 | + |
| 35 | +// 2. Give your data a stable reference |
| 36 | +const data: Array<Person> = [ |
| 37 | + { firstName: 'tanner', lastName: 'linsley', age: 24 }, |
| 38 | + { firstName: 'tandy', lastName: 'miller', age: 40 }, |
| 39 | + { firstName: 'joe', lastName: 'dirte', age: 45 }, |
| 40 | +] |
| 41 | + |
| 42 | +// 3. New in v9: declare which features this table uses |
| 43 | +const features = tableFeatures({ |
| 44 | + coreReactivityFeature: storeReactivityBindings(), |
| 45 | +}) |
| 46 | + |
| 47 | +// 4. Define your columns. Renderers return values or HTML strings. |
| 48 | +const columns: Array<ColumnDef<typeof features, Person>> = [ |
| 49 | + { |
| 50 | + accessorKey: 'firstName', // accessorKey shorthand |
| 51 | + header: 'First Name', |
| 52 | + cell: (info) => info.getValue(), |
| 53 | + }, |
| 54 | + { |
| 55 | + accessorFn: (row) => row.lastName, // accessorFn alternative with a custom id |
| 56 | + id: 'lastName', |
| 57 | + header: () => '<span>Last Name</span>', |
| 58 | + cell: (info) => `<i>${info.getValue<string>()}</i>`, |
| 59 | + }, |
| 60 | + { |
| 61 | + accessorKey: 'age', |
| 62 | + header: () => 'Age', |
| 63 | + }, |
| 64 | +] |
| 65 | + |
| 66 | +// 5. Create the table instance |
| 67 | +const table = constructTable({ |
| 68 | + features, |
| 69 | + columns, |
| 70 | + data, |
| 71 | +}) |
| 72 | + |
| 73 | +const app = document.getElementById('app') |
| 74 | + |
| 75 | +if (!app) { |
| 76 | + throw new Error('Missing #app element') |
| 77 | +} |
| 78 | + |
| 79 | +// 6. Render markup from the table instance APIs |
| 80 | +const renderTable = () => { |
| 81 | + const tableElement = document.createElement('table') |
| 82 | + const thead = document.createElement('thead') |
| 83 | + const tbody = document.createElement('tbody') |
| 84 | + |
| 85 | + table.getHeaderGroups().forEach((headerGroup) => { |
| 86 | + const tr = document.createElement('tr') |
| 87 | + |
| 88 | + headerGroup.headers.forEach((header) => { |
| 89 | + const th = document.createElement('th') |
| 90 | + th.innerHTML = header.isPlaceholder |
| 91 | + ? '' |
| 92 | + : String(FlexRender({ header }) ?? '') |
| 93 | + tr.appendChild(th) |
| 94 | + }) |
| 95 | + |
| 96 | + thead.appendChild(tr) |
| 97 | + }) |
| 98 | + |
| 99 | + table.getRowModel().rows.forEach((row) => { |
| 100 | + const tr = document.createElement('tr') |
| 101 | + |
| 102 | + row.getAllCells().forEach((cell) => { |
| 103 | + const td = document.createElement('td') |
| 104 | + td.innerHTML = String(FlexRender({ cell }) ?? '') |
| 105 | + tr.appendChild(td) |
| 106 | + }) |
| 107 | + |
| 108 | + tbody.appendChild(tr) |
| 109 | + }) |
| 110 | + |
| 111 | + tableElement.appendChild(thead) |
| 112 | + tableElement.appendChild(tbody) |
| 113 | + |
| 114 | + app.replaceChildren(tableElement) |
| 115 | +} |
| 116 | + |
| 117 | +table.store.subscribe(() => renderTable()) |
| 118 | +renderTable() |
| 119 | +``` |
| 120 | + |
| 121 | +A few things to note: |
| 122 | + |
| 123 | +- `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. |
| 124 | +- 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. |
| 125 | +- 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. |
| 126 | +- `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. |
| 127 | +- Subscribe to `table.store` when your UI should redraw after table state changes. |
| 128 | + |
| 129 | +See the full [Basic example](./examples/basic) for a runnable version with more columns and a footer. |
| 130 | + |
| 131 | +## Add a Feature: Sorting |
| 132 | + |
| 133 | +Features are opt-in in v9. To make columns sortable, register `rowSortingFeature` and the `sortedRowModel` factory in `tableFeatures`, then wire the header click handler. |
| 134 | + |
| 135 | +```ts |
| 136 | +import { |
| 137 | + constructTable, |
| 138 | + createSortedRowModel, |
| 139 | + rowSortingFeature, |
| 140 | + sortFns, |
| 141 | + tableFeatures, |
| 142 | +} from '@tanstack/table-core' |
| 143 | +import { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings' |
| 144 | + |
| 145 | +const features = tableFeatures({ |
| 146 | + coreReactivityFeature: storeReactivityBindings(), |
| 147 | + rowSortingFeature, // enables sorting APIs and state |
| 148 | + sortedRowModel: createSortedRowModel(), // client-side sorting |
| 149 | + sortFns, |
| 150 | +}) |
| 151 | + |
| 152 | +// columns and data unchanged from above |
| 153 | + |
| 154 | +const table = constructTable({ |
| 155 | + features, |
| 156 | + columns, |
| 157 | + data, |
| 158 | +}) |
| 159 | +``` |
| 160 | + |
| 161 | +```ts |
| 162 | +// In renderTable, replace the header rendering with sortable header markup. |
| 163 | +table.getHeaderGroups().forEach((headerGroup) => { |
| 164 | + const tr = document.createElement('tr') |
| 165 | + |
| 166 | + headerGroup.headers.forEach((header) => { |
| 167 | + const th = document.createElement('th') |
| 168 | + const button = document.createElement('button') |
| 169 | + |
| 170 | + button.type = 'button' |
| 171 | + button.disabled = !header.column.getCanSort() |
| 172 | + button.innerHTML = header.isPlaceholder |
| 173 | + ? '' |
| 174 | + : String(FlexRender({ header }) ?? '') |
| 175 | + button.innerHTML += |
| 176 | + { |
| 177 | + asc: ' 🔼', |
| 178 | + desc: ' 🔽', |
| 179 | + }[header.column.getIsSorted() as string] ?? '' |
| 180 | + button.addEventListener('click', (event) => { |
| 181 | + header.column.getToggleSortingHandler()?.(event) |
| 182 | + }) |
| 183 | + |
| 184 | + th.appendChild(button) |
| 185 | + tr.appendChild(th) |
| 186 | + }) |
| 187 | + |
| 188 | + thead.appendChild(tr) |
| 189 | +}) |
| 190 | +``` |
| 191 | + |
| 192 | +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](./examples/sorting) for custom sort functions, multi-sorting, and per-column options. |
| 193 | + |
| 194 | +## Where to Go Next |
| 195 | + |
| 196 | +**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](./guide/table-state). It is the foundational guide for vanilla usage. |
| 197 | + |
| 198 | +**Feature examples.** Browse runnable vanilla examples such as [Pagination](./examples/pagination) and [Sorting](./examples/sorting) to see complete DOM rendering setups. |
| 199 | + |
| 200 | +**Core guides.** The framework-agnostic guides cover the main table objects: [Data](../../guide/data), [Column Definitions](../../guide/column-defs), [Table Instance](../../guide/tables), [Rows](../../guide/rows), [Cells](../../guide/cells), and [Header Groups](../../guide/header-groups). |
0 commit comments