Want to skip to the implementation? Check out these Lit examples:
import { LitElement, html } from 'lit'
import { customElement, state } from 'lit/decorators.js'
import { TableController, tableFeatures, columnGroupingFeature, createGroupedRowModel, aggregationFns } from '@tanstack/lit-table'
const features = tableFeatures({
columnGroupingFeature,
groupedRowModel: createGroupedRowModel(),
aggregationFns,
})
@customElement('my-table')
class MyTable extends LitElement {
@state()
private data = defaultData
private tableController = new TableController(this)
protected render() {
const table = this.tableController.table({
features,
columns,
data: this.data,
})
return html`...`
}
}import { LitElement, html } from 'lit'
import { customElement, state } from 'lit/decorators.js'
import { TableController, tableFeatures, columnGroupingFeature, createGroupedRowModel, aggregationFns } from '@tanstack/lit-table'
const features = tableFeatures({
columnGroupingFeature,
groupedRowModel: createGroupedRowModel(),
aggregationFns,
})
@customElement('my-table')
class MyTable extends LitElement {
@state()
private data = defaultData
private tableController = new TableController(this)
protected render() {
const table = this.tableController.table({
features,
columns,
data: this.data,
})
return html`...`
}
}Grouping in TanStack table is a feature that applies to columns and allows you to categorize and organize the table rows based on specific columns. This can be useful in cases where you have a large amount of data and you want to group them together based on certain criteria.
Grouping can also affect column order. There are 3 table features that can reorder columns, which happen in the following order:
To use the grouping feature, add the columnGroupingFeature to your features and the groupedRowModel to your row models. The grouped row model is responsible for grouping the rows based on the grouping state.
import {
TableController,
tableFeatures,
columnGroupingFeature,
createGroupedRowModel,
aggregationFns,
} from '@tanstack/lit-table'
const features = tableFeatures({
columnGroupingFeature,
groupedRowModel: createGroupedRowModel(),
aggregationFns,
})
const table = this.tableController.table({
features,
// other options...
})import {
TableController,
tableFeatures,
columnGroupingFeature,
createGroupedRowModel,
aggregationFns,
} from '@tanstack/lit-table'
const features = tableFeatures({
columnGroupingFeature,
groupedRowModel: createGroupedRowModel(),
aggregationFns,
})
const table = this.tableController.table({
features,
// other options...
})When grouping state is active, the table will add matching rows as subRows to the grouped row. The grouped row will be added to the table rows at the same index as the first matching row. The matching rows will be removed from the table rows. To allow the user to expand and collapse the grouped rows, you can use the expanding feature.
const features = tableFeatures({
columnGroupingFeature,
rowExpandingFeature,
groupedRowModel: createGroupedRowModel(),
expandedRowModel: createExpandedRowModel(),
aggregationFns,
})
const table = this.tableController.table({
features,
// other options...
})const features = tableFeatures({
columnGroupingFeature,
rowExpandingFeature,
groupedRowModel: createGroupedRowModel(),
expandedRowModel: createExpandedRowModel(),
aggregationFns,
})
const table = this.tableController.table({
features,
// other options...
})The grouping state is an array of strings, where each string is the ID of a column to group by. The order of the strings in the array determines the order of the grouping. For example, if the grouping state is ['column1', 'column2'], then the table will first group by column1, and then within each group, it will group by column2. You can control the grouping state using the setGrouping function:
table.setGrouping(['column1', 'column2']);table.setGrouping(['column1', 'column2']);You can also reset the grouping state to its initial state using the resetGrouping function:
table.resetGrouping();table.resetGrouping();By default, when a column is grouped, it is moved to the start of the table. You can control this behavior using the groupedColumnMode option. If you set it to 'reorder', then the grouped columns will be moved to the start of the table. If you set it to 'remove', then the grouped columns will be removed from the table. If you set it to false, then the grouped columns will not be moved or removed.
const table = this.tableController.table({
features,
// other options...
groupedColumnMode: 'reorder',
})const table = this.tableController.table({
features,
// other options...
groupedColumnMode: 'reorder',
})When rows are grouped, you can aggregate the data in the grouped rows by columns using the aggregationFn column option. This is a string that is the name of a built-in aggregation function, or a custom aggregation function registered in the aggregationFns slot on tableFeatures.
const column = columnHelper.accessor('key', {
aggregationFn: 'sum',
})const column = columnHelper.accessor('key', {
aggregationFn: 'sum',
})In the above example, the sum aggregation function will be used to aggregate the data in the grouped rows. By default, numeric columns will use the sum aggregation function, and non-numeric columns will use the count aggregation function. You can override this behavior by specifying the aggregationFn option in the column definition.
There are several built-in aggregation functions that you can use:
You can define custom aggregation functions in the aggregationFns slot on tableFeatures. The slot is a record where the keys are the names of the aggregation functions and the values are the aggregation functions themselves. You can then reference these aggregation functions by name in a column's aggregationFn option.
const features = tableFeatures({
columnGroupingFeature,
groupedRowModel: createGroupedRowModel(),
aggregationFns: {
...aggregationFns,
myCustomAggregation: (columnId, leafRows, childRows) => {
// return the aggregated value
},
},
})
const table = this.tableController.table({
features,
// other options...
})const features = tableFeatures({
columnGroupingFeature,
groupedRowModel: createGroupedRowModel(),
aggregationFns: {
...aggregationFns,
myCustomAggregation: (columnId, leafRows, childRows) => {
// return the aggregated value
},
},
})
const table = this.tableController.table({
features,
// other options...
})In the above example, myCustomAggregation is a custom aggregation function that takes the column ID, the leaf rows, and the child rows, and returns the aggregated value. You can then use this aggregation function in a column's aggregationFn option:
const column = columnHelper.accessor('key', {
aggregationFn: 'myCustomAggregation',
})const column = columnHelper.accessor('key', {
aggregationFn: 'myCustomAggregation',
})TypeScript Note: String references like aggregationFn: 'myCustomAggregation' are automatically typed when the function is registered in the aggregationFns slot on tableFeatures. The registry slot replaces the old declare module augmentation approach. Alternatively, skip the registry entirely by passing the function directly to the aggregationFn column option.
If you are doing server-side grouping and aggregation, you can enable manual grouping using the manualGrouping option. When this option is set to true, the table will not automatically group rows using getGroupedRowModel() and instead will expect you to manually group the rows before passing them to the table.
const features = tableFeatures({ columnGroupingFeature })
const table = this.tableController.table({
features,
// other options...
manualGrouping: true,
})const features = tableFeatures({ columnGroupingFeature })
const table = this.tableController.table({
features,
// other options...
manualGrouping: true,
})Note: There are not currently many known easy ways to do server-side grouping with TanStack Table. You will need to do lots of custom cell rendering to make this work.
If you need access to the grouping state in other parts of your application, you can own the grouping state slice yourself. The recommended way in v9 is an external atom passed through the atoms table option. Atoms preserve fine-grained subscriptions, and the grouping value can be read or subscribed to from any module (such as in a query key for server-side grouping) without going through the component that owns the table.
import { createAtom } from '@tanstack/store'
import type { GroupingState } from '@tanstack/lit-table'
// create a stable atom at module scope (or in a shared store module)
const groupingAtom = createAtom<GroupingState>([])
const table = this.tableController.table({
features,
// other options...
atoms: {
grouping: groupingAtom, // grouping APIs now update groupingAtom
},
})
// read groupingAtom.get() (or subscribe to groupingAtom) wherever you need the valueimport { createAtom } from '@tanstack/store'
import type { GroupingState } from '@tanstack/lit-table'
// create a stable atom at module scope (or in a shared store module)
const groupingAtom = createAtom<GroupingState>([])
const table = this.tableController.table({
features,
// other options...
atoms: {
grouping: groupingAtom, // grouping APIs now update groupingAtom
},
})
// read groupingAtom.get() (or subscribe to groupingAtom) wherever you need the valueAlternatively, the v8-style state.grouping plus onGroupingChange pattern is still supported. It can be convenient for simple integrations or when migrating v8 code, but it is less fine-grained than external atoms. See the Table State Guide for a deeper comparison.
@state()
private grouping: GroupingState = []
const table = this.tableController.table({
features,
// other options...
state: {
grouping: this.grouping,
},
onGroupingChange: (updater) => {
this.grouping = typeof updater === 'function' ? updater(this.grouping) : updater
},
})@state()
private grouping: GroupingState = []
const table = this.tableController.table({
features,
// other options...
state: {
grouping: this.grouping,
},
onGroupingChange: (updater) => {
this.grouping = typeof updater === 'function' ? updater(this.grouping) : updater
},
})Columns expose grouping APIs for toggling grouping and building grouping UI:
column.toggleGrouping()
column.getToggleGroupingHandler()
column.getCanGroup()
column.getIsGrouped()
column.getGroupedIndex()
column.getAutoAggregationFn()
column.getAggregationFn()column.toggleGrouping()
column.getToggleGroupingHandler()
column.getCanGroup()
column.getIsGrouped()
column.getGroupedIndex()
column.getAutoAggregationFn()
column.getAggregationFn()Rows expose grouping helpers for grouped row rendering:
row.getIsGrouped()
row.getGroupingValue(columnId)
row.groupingColumnId
row.groupingValuerow.getIsGrouped()
row.getGroupingValue(columnId)
row.groupingColumnId
row.groupingValueCells expose helpers for choosing between grouped, aggregated, placeholder, and normal cell rendering:
cell.getIsGrouped()
cell.getIsAggregated()
cell.getIsPlaceholder()cell.getIsGrouped()
cell.getIsAggregated()
cell.getIsPlaceholder()The table instance exposes grouped and pre-grouped row models:
table.getGroupedRowModel()
table.getPreGroupedRowModel()table.getGroupedRowModel()
table.getPreGroupedRowModel()Use table.setGrouping and table.resetGrouping to update the grouping state directly.