Docs
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
API Reference
Table API Reference
Column API Reference
Row API Reference
Cell API Reference
Header API Reference
Features API Reference
Enterprise
Basic Examples
Specialized Examples

Lit Example: Sorting Dynamic Data

ts
import { customElement } from 'lit/decorators.js'
import { html, LitElement, PropertyValueMap } from 'lit'
import { repeat } from 'lit/directives/repeat.js'
import { state } from 'lit/decorators/state.js'
import {
  ColumnDef,
  flexRender,
  createSortedRowModel,
  TableController,
  rowSortingFeature,
  sortFns,
  tableFeatures,
} from '@tanstack/lit-table'
import type { SortFn } from '@tanstack/lit-table'

import { makeData, Person } from './makeData'

const _features = tableFeatures({
  rowSortingFeature,
})

const sortStatusFn: SortFn<typeof _features, Person> = (
  rowA,
  rowB,
  _columnId,
) => {
  const statusA = rowA.original.status
  const statusB = rowB.original.status
  const statusOrder = ['single', 'complicated', 'relationship']
  return statusOrder.indexOf(statusA) - statusOrder.indexOf(statusB)
}

const columns: ColumnDef<typeof _features, Person>[] = [
  {
    accessorKey: 'firstName',
    cell: (info) => info.getValue(),
    //this column will sort in ascending order by default since it is a string column
  },
  {
    accessorFn: (row) => row.lastName,
    id: 'lastName',
    cell: (info) => info.getValue(),
    header: () => html`<span>Last Name</span>`,
    sortUndefined: 'last', //force undefined values to the end
    sortDescFirst: false, //first sort order will be ascending (nullable values can mess up auto detection of sort order)
  },
  {
    accessorKey: 'age',
    header: () => 'Age',
    //this column will sort in descending order by default since it is a number column
  },
  {
    accessorKey: 'visits',
    header: () => html`<span>Visits</span>`,
    sortUndefined: 'last', //force undefined values to the end
  },
  {
    accessorKey: 'status',
    header: 'Status',
    sortFn: sortStatusFn, //use our custom sorting function for this enum column
  },
  {
    accessorKey: 'progress',
    header: 'Profile Progress',
    // enableSorting: false, //disable sorting for this column
  },
  {
    accessorKey: 'rank',
    header: 'Rank',
    invertSorting: true, //invert the sorting order (golf score-like where smaller is better)
  },
  {
    accessorKey: 'createdAt',
    header: 'Created At',
    // sortingFn: 'datetime' //make sure table knows this is a datetime column (usually can detect if no null values)
  },
]

const data: Person[] = makeData(1000)

@customElement('lit-table-example')
class LitTableExample extends LitElement {
  @state()
  private _multiplier: number = 1

  @state()
  private _data: Person[] = new Array<Person>()

  private tableController = new TableController<typeof _features, Person>(this)

  constructor() {
    super()
    this._data = [...data]
  }

  protected willUpdate(
    _changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
  ): void {
    super.willUpdate(_changedProperties)
    if (_changedProperties.has('_multiplier')) {
      const newData: Person[] = data.map((d) => {
        const p: Person = {
          ...d,
          visits: d.visits ? d.visits * this._multiplier : undefined,
        }
        return p
      })
      this._data.length = 0
      this._data = newData
    }
  }
  protected render() {
    const table = this.tableController.table(
      {
        _features,
        _rowModels: {
          sortedRowModel: createSortedRowModel(sortFns),
        },
        columns,
        data: this._data,
      },
      (state) => ({ sorting: state.sorting }),
    )

    return html`
      <input
        type="number"
        min="1"
        max="100"
        id="multiplier"
        @change="${(e: Event) => {
          const inputElement = (e as CustomEvent).target as HTMLInputElement
          if (inputElement) {
            this._multiplier = +inputElement.value
            this.requestUpdate('_multiplier')
          }
        }}"
      />
      <table>
        <thead>
          ${repeat(
            table.getHeaderGroups(),
            (headerGroup) => headerGroup.id,
            (headerGroup) => html`
              <tr>
                ${headerGroup.headers.map(
                  (header) => html`
                    <th colspan="${header.colSpan}">
                      ${header.isPlaceholder
                        ? null
                        : html`<div
                            title=${header.column.getCanSort()
                              ? header.column.getNextSortingOrder() === 'asc'
                                ? 'Sort ascending'
                                : header.column.getNextSortingOrder() === 'desc'
                                  ? 'Sort descending'
                                  : 'Clear sort'
                              : undefined}
                            @click="${header.column.getToggleSortingHandler()}"
                            style="cursor: ${header.column.getCanSort()
                              ? 'pointer'
                              : 'not-allowed'}"
                          >
                            ${flexRender(
                              header.column.columnDef.header,
                              header.getContext(),
                            )}
                            ${{ asc: ' 🔼', desc: ' 🔽' }[
                              header.column.getIsSorted() as string
                            ] ?? null}
                          </div>`}
                    </th>
                  `,
                )}
              </tr>
            `,
          )}
        </thead>
        <tbody>
          ${table
            .getRowModel()
            .rows.slice(0, 10)
            .map(
              (row) => html`
                <tr>
                  ${row
                    .getVisibleCells()
                    .map(
                      (cell) => html`
                        <td>
                          ${flexRender(
                            cell.column.columnDef.cell,
                            cell.getContext(),
                          )}
                        </td>
                      `,
                    )}
                </tr>
              `,
            )}
        </tbody>
      </table>
      <pre>${JSON.stringify(table.state.sorting, null, 2)}</pre>
      <style>
        * {
          font-family: sans-serif;
          font-size: 14px;
          box-sizing: border-box;
        }

        table {
          border: 1px solid lightgray;
          border-collapse: collapse;
        }

        tbody {
          border-bottom: 1px solid lightgray;
        }

        th {
          border-bottom: 1px solid lightgray;
          border-right: 1px solid lightgray;
          padding: 2px 4px;
        }

        tfoot {
          color: gray;
        }

        tfoot th {
          font-weight: normal;
        }
      </style>
    `
  }
}