CtrlK
BlogDocsLog inGet started
Tessl Logo

getting-started

Create a TanStack Lit Table v9 table with a stable TableController host field, explicit tableFeatures, controller.table(options, selector) during render, and headless Lit templates. Load for first-table setup, TableController lifecycle, FlexRender, or adapting a React example to Lit.

72

Quality

91%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

This skill builds on @tanstack/table-core#core and @tanstack/table-core#table-features. Read them first for the headless and feature-plugin model.

Setup

import { LitElement, html } from 'lit'
import { customElement, state } from 'lit/decorators.js'
import { repeat } from 'lit/directives/repeat.js'
import {
  FlexRender,
  TableController,
  createColumnHelper,
  tableFeatures,
} from '@tanstack/lit-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' }),
])

@customElement('people-table')
export class PeopleTable extends LitElement {
  @state() private people: Array<Person> = [{ id: '1', name: 'Ada' }]
  private tableController = new TableController<typeof features, Person>(this)

  protected render() {
    const table = this.tableController.table({
      features,
      columns,
      data: this.people,
      getRowId: (row) => row.id,
    })

    return html`<table>
      <thead>
        ${repeat(
          table.getHeaderGroups(),
          (group) => group.id,
          (group) =>
            html`<tr>
              ${repeat(
                group.headers,
                (header) => header.id,
                (header) =>
                  html`<th>
                    ${header.isPlaceholder ? null : FlexRender({ header })}
                  </th>`,
              )}
            </tr>`,
        )}
      </thead>
      <tbody>
        ${repeat(
          table.getRowModel().rows,
          (row) => row.id,
          (row) =>
            html`<tr>
              ${repeat(
                row.getAllCells(),
                (cell) => cell.id,
                (cell) => html`<td>${FlexRender({ cell })}</td>`,
              )}
            </tr>`,
        )}
      </tbody>
    </table>`
  }
}

Core Patterns

Keep static table infrastructure outside render

Create features, column helpers, and static columns at module scope. Keep one TableController as a host field; call its table method during each render with current options.

Select only state the host renders

const table = this.tableController.table(
  { features, columns, data: this.people },
  (state) => ({ pagination: state.pagination }),
)

Use the default selector for simple tables. Narrow it only when host updates are measurably expensive.

Treat markup and CSS as application code

Table supplies models and render values. Use semantic elements, accessibility behavior, widths, sticky positioning, and design-system components in the Lit template.

Common Mistakes

HIGH Recreating the controller during render

Wrong:

protected render() {
  const controller = new TableController<typeof features, Person>(this)
  return html`${controller.table({ features, columns, data: this.people }).getRowModel().rows.length}`
}

Correct: keep private tableController = new TableController(this) as a class field and reuse it.

Each controller registers with the host and owns subscriptions; recreating it leaks lifecycle work and loses stable table state.

Source: TanStack/table:packages/lit-table/src/TableController.ts

HIGH Passing v8 options to the constructor

Wrong: new TableController(this, () => ({ data, columns })).

Correct: construct with the host only, then call this.tableController.table({ features, data, columns }) during render.

The v9 controller receives current options through table, not a constructor thunk.

Source: TanStack/table:docs/framework/lit/guide/migrating.md

HIGH Expecting feature state to render UI

Wrong: enable column pinning and assume cells become sticky.

Correct: render the appropriate start/center/end collections and apply sticky offsets and CSS in the template.

TanStack Table is headless; state and models never inject markup or styles.

Source: TanStack/table:docs/overview.md

API Discovery

Inspect node_modules/@tanstack/lit-table/dist/index.d.ts and the exported implementation. Core table and feature APIs are in node_modules/@tanstack/table-core/dist/.

Repository
TanStack/table
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.