---
name: getting-started
description: >
  Create a TanStack Ember Table v9 table with useTable, a tracked options thunk, stable tableFeatures and columns, .gts templates, FlexRenderCell/Header/Footer, and correctly bound template helpers. Load for first-table setup, Glimmer reactivity, component cell renderers, or adapting another framework's example to Ember.
metadata:
  type: framework
  library: '@tanstack/ember-table'
  framework: ember
  library_version: '9.0.0-beta.78'
requires:
  - '@tanstack/table-core#core'
  - '@tanstack/table-core#table-features'
sources:
  - 'TanStack/table:docs/framework/ember/quick-start.md'
  - 'TanStack/table:examples/ember/basic-table'
  - 'TanStack/table:packages/ember-table/src/index.ts'
  - 'TanStack/table:packages/ember-table/src/use-table.ts'
  - 'TanStack/table:packages/ember-table/src/FlexRender.gts'
---

This skill builds on `@tanstack/table-core#core` and `@tanstack/table-core#table-features`. Ember Table is headless: it supplies reactive table models and renderer helpers, while the application owns semantic markup, CSS, accessibility, and design-system components.

The v9 addon requires Ember 5.8 or newer with Embroider or ember-auto-import v2. Prefer `.gts`/`.gjs` template-tag components with Glint.

## Setup

```gts
import Component from '@glimmer/component'
import { tracked } from '@glimmer/tracking'
import { on } from '@ember/modifier'
import {
  FlexRenderCell,
  FlexRenderHeader,
  createColumnHelper,
  tableFeatures,
  useTable,
  type Cell,
  type Row,
} from '@tanstack/ember-table'

type Person = { id: string; name: string }

const features = tableFeatures({})
const columnHelper = createColumnHelper<typeof features, Person>()
const columns = columnHelper.columns([
  columnHelper.accessor('name', { header: 'Name' }),
])
const initialData: Person[] = [{ id: '1', name: 'Ada' }]
const getRowId = (row: Person) => row.id

const getAllCells = (
  row: Row<typeof features, Person>,
): Array<Cell<typeof features, Person>> => row.getAllCells()

export default class PeopleTable extends Component {
  @tracked data = initialData

  table = useTable(() => ({
    features,
    columns,
    data: this.data,
    getRowId,
  }))

  get headerGroups() {
    return this.table.getHeaderGroups()
  }

  get rows() {
    return this.table.getRowModel().rows
  }

  addPerson = () => {
    this.data = [...this.data, { id: '2', name: 'Grace' }]
  }

  <template>
    <button type='button' {{on 'click' this.addPerson}}>Add person</button>
    <table>
      <thead>
        {{#each this.headerGroups as |group|}}
          <tr>
            {{#each group.headers as |header|}}
              <th colspan={{header.colSpan}}>
                {{#unless header.isPlaceholder}}
                  <FlexRenderHeader @header={{header}} />
                {{/unless}}
              </th>
            {{/each}}
          </tr>
        {{/each}}
      </thead>
      <tbody>
        {{#each this.rows as |row|}}
          <tr>
            {{#each (getAllCells row) as |cell|}}
              <td><FlexRenderCell @cell={{cell}} /></td>
            {{/each}}
          </tr>
        {{/each}}
      </tbody>
    </table>
  </template>
}
```

`useTable` takes an options thunk. Tracked properties read by that thunk update table options, and table API reads inside getters/templates participate in Glimmer tracking. Keep `features` and `columns` at stable module or component-lifetime scope; replace `@tracked data` only when data meaningfully changes.

## Ember-Specific Patterns

### Preserve method receivers in templates

V9 table, column, row, cell, and header methods live on prototypes and require their receiver. Ember templates extract function references, so call methods in a getter or a small module-level helper:

```gts
const getCanSort = (column: Column<typeof features, Person>) =>
  column.getCanSort()

const toggleSort =
  (column: Column<typeof features, Person>) => (event: Event) =>
    column.getToggleSortingHandler()?.(event)
```

Pass `toggleSort header.column` to `{{on}}`; do not pass an extracted Table method directly.

### Render definitions through the matching component

Use `FlexRenderCell`, `FlexRenderHeader`, and `FlexRenderFooter` with their matching object. A definition may return a primitive or `flexRenderComponent(Component, options)`. The rendered component receives `@ctx` and optional `@options`; Table does not instantiate arbitrary component-library markup for you.

## Common Mistakes

### HIGH Passing an options object instead of a thunk

Wrong: `useTable({ features, columns, data: this.data })`.

Correct: `useTable(() => ({ features, columns, data: this.data }))`.

The thunk is how tracked option reads are connected to the table.

### HIGH Passing an unbound prototype method to a template modifier

Wrong: `{{on 'click' header.column.getToggleSortingHandler}}`.

Correct: wrap the call in a helper that invokes the method on `header.column`, as shown above.

An extracted v9 prototype method loses `this` and can throw or silently target the wrong receiver.

### HIGH Recreating model inputs inside the options thunk

Wrong: `columns: columnHelper.columns(...)` or `data: this.data.slice()` inside `useTable(() => ...)`.

Correct: keep columns stable and pass the tracked data reference directly. The thunk may rerun; it should not manufacture new model inputs on every tracked update.

### MEDIUM Using a visibility-aware API without its feature

With `tableFeatures({})`, render `row.getAllCells()`. Add `columnVisibilityFeature` before using `row.getVisibleCells()`.

## API Discovery

Inspect `node_modules/@tanstack/ember-table/declarations/index.d.ts` for exports, `use-table.d.ts` for options/reactivity behavior, `FlexRender.d.ts` and `flex-render.d.ts` for renderer contracts, and `node_modules/@tanstack/table-core/dist/features/<feature>/` for feature-gated APIs. Do not substitute React hooks, subscriptions, or component signatures.
