createTableHook creates an app-specific table factory. Use it to define shared features, row models, and default table options once, then create each Alpine table with the columns and data that are unique to that table.
Note
Unlike the React, Solid, Lit, and Svelte adapters, the Alpine createTableHook does not register reusable cell/header/table components. Alpine renders cell and header content as HTML strings through x-html, and Alpine has no component primitive to bind, so the hook is focused on sharing features and default options. Reusable interactive markup is expressed with Alpine.bind bundles in your templates instead.
Create one app table hook and put the feature set, row models, and shared defaults there. This example makes sorting available to every table created by createAppTable.
import {
createSortedRowModel,
createTableHook,
rowSortingFeature,
sortFns,
tableFeatures,
} from '@tanstack/alpine-table'
const features = tableFeatures({
rowSortingFeature,
sortedRowModel: createSortedRowModel(),
sortFns,
})
const { createAppTable, createAppColumnHelper } = createTableHook({
features,
debugTable: true,
enableSortingRemoval: false,
})Options passed to createTableHook become defaults for every table created by createAppTable. The features option is also bound to the returned column helper, so column definitions know that sorting APIs are available.
Create one column helper per row type. The helper is already bound to your app's feature set, so each table does not need to thread typeof features through its column definitions. Renderers return HTML strings, which are rendered with x-html via table.FlexRender.
type Person = {
firstName: string
lastName: string
age: number
visits: number
}
const columnHelper = createAppColumnHelper<Person>()
const columns = columnHelper.columns([
columnHelper.accessor('firstName', {
cell: (info) => info.getValue(),
}),
columnHelper.accessor((row) => row.lastName, {
id: 'lastName',
header: () => '<span>Last Name</span>',
cell: (info) => `<i>${info.getValue()}</i>`,
}),
columnHelper.accessor('age', {
header: 'Age',
}),
columnHelper.accessor('visits', {
header: 'Visits',
}),
])Create each table with createAppTable inside an Alpine.data component. The call site provides table-specific inputs such as columns and reactive data; shared features and defaults come from the hook.
import Alpine from 'alpinejs'
Alpine.data('table', () => {
const local = Alpine.reactive({ data: [] as Array<Person> })
const table = createAppTable({
columns,
get data() {
return local.data
},
})
return { table }
})
window.Alpine = Alpine
Alpine.start()You render the table with the same table instance APIs used by a standalone createTable table. table.FlexRender is attached to the instance, so you do not need to import the top-level helper. Attach the sort click handler to a real element (Alpine does not initialize directives inside x-html).
<div x-data="table">
<table>
<thead>
<template
x-for="headerGroup in table.getHeaderGroups()"
:key="headerGroup.id"
>
<tr>
<template x-for="header in headerGroup.headers" :key="header.id">
<th @click="header.column.getToggleSortingHandler()?.($event)">
<span x-html="table.FlexRender({ header })"></span>
</th>
</template>
</tr>
</template>
</thead>
<tbody>
<template x-for="row in table.getRowModel().rows" :key="row.id">
<tr>
<template x-for="cell in row.getAllCells()" :key="cell.id">
<td x-html="table.FlexRender({ cell })"></td>
</template>
</tr>
</template>
</tbody>
</table>
</div>Options passed to createAppTable override defaults from createTableHook. Use this for the few tables that need different behavior without creating a separate app hook.
const table = createAppTable({
columns,
get data() {
return local.data
},
enableSortingRemoval: true, // override the hook default for this table only
})Use createTableHook when multiple tables should share features, row models, default options, or conventions. Use the standalone createTable API for a one-off table.