Row Grouping Plugin
The Row Grouping plugin organizes your rows into collapsible hierarchical groups. Perfect for organizing data by category, department, status, or any other dimension—or even multiple dimensions for nested grouping.
Installation
Section titled “Installation”import '@toolbox-web/grid/features/grouping-rows';Basic Usage
Section titled “Basic Usage”The groupOn callback receives each row and should return an array representing the group path. For single-level grouping, return a one-element array. For multi-level grouping, return multiple elements (e.g., ['Region', 'Department']).
import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid('tbw-grid');grid.gridConfig = { columns: [ { field: 'name', header: 'Employee' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'currency' } ], features: { groupingRows: { groupOn: (row) => [row.department], showRowCount: true, defaultExpanded: false, }, },};
grid.rows = employees;import '@toolbox-web/grid-react/features/grouping-rows';import { DataGrid } from '@toolbox-web/grid-react';
function EmployeeGrid({ employees }) { return ( <DataGrid rows={employees} columns={[ { field: 'name', header: 'Employee' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'currency' } ]} groupingRows={{ groupOn: (row) => row.department, showRowCount: true }} /> );}<script setup>import '@toolbox-web/grid-vue/features/grouping-rows';import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';
const employees = [ { name: 'Alice', department: 'Engineering', salary: 95000 }, { name: 'Bob', department: 'Marketing', salary: 75000 }, { name: 'Carol', department: 'Engineering', salary: 90000 },];</script>
<template> <TbwGrid :rows="employees" :grouping-rows="{ groupOn: (row) => row.department, showRowCount: true }"> <TbwGridColumn field="name" header="Employee" /> <TbwGridColumn field="department" header="Department" /> <TbwGridColumn field="salary" header="Salary" type="currency" /> </TbwGrid></template>// Feature import - enables the [groupingRows] inputimport { GridGroupingRowsDirective } from '@toolbox-web/grid-angular/features/grouping-rows';import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import type { ColumnConfig } from '@toolbox-web/grid';
@Component({ selector: 'app-employee-grid', imports: [Grid, GridGroupingRowsDirective], template: ` <tbw-grid [rows]="employees" [columns]="columns" [groupingRows]="groupingConfig" style="height: 400px; display: block;"> </tbw-grid> `,})export class EmployeeGridComponent { employees = [/* employee data */];
columns: ColumnConfig[] = [ { field: 'name', header: 'Employee' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'currency' } ];
groupingConfig = { groupOn: (row: any) => [row.department], showRowCount: true, };}Default Grouping
Section titled “Default Grouping”<tbw-grid style="height: 400px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/grouping-rows';
const sampleData = [ { id: 1, name: 'Alice', department: 'Engineering', salary: 95000 }, { id: 2, name: 'Bob', department: 'Marketing', salary: 75000 }, { id: 3, name: 'Carol', department: 'Engineering', salary: 105000 }, { id: 4, name: 'Dan', department: 'Sales', salary: 85000 }, { id: 5, name: 'Eve', department: 'Marketing', salary: 72000 }, { id: 6, name: 'Frank', department: 'Engineering', salary: 98000 }, { id: 7, name: 'Grace', department: 'Sales', salary: 88000 },];const columns = [ { field: 'id', header: 'ID', type: 'number' }, { field: 'name', header: 'Name' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'number' },];
const grid = queryGrid('tbw-grid');
function rebuild(opts: Record<string, unknown>) { const accordion = opts.accordion ?? false; const expandRaw = opts.defaultExpanded; const defaultExpanded = accordion && expandRaw === 'all' ? false : expandRaw === 'all' ? true : expandRaw === 'none' ? false : expandRaw;
grid.gridConfig = { columns, features: { groupingRows: { animation: (opts.animation) ?? 'slide', groupOn: (row: { department: string }) => row.department, defaultExpanded, showRowCount: opts.showRowCount ?? true, indentWidth: (opts.indentWidth) ?? 20, fullWidth: true, accordion, }, }, }; grid.rows = sampleData;}
rebuild({ animation: 'slide', defaultExpanded: 'none', showRowCount: true, accordion: false, indentWidth: 20 });Configuration Options
Section titled “Configuration Options”See GroupingRowsConfig for the full list of options and their defaults.
Default Expanded Options
Section titled “Default Expanded Options”The defaultExpanded option controls which groups are expanded on initial render:
// Expand all groupsfeatures: { groupingRows: { defaultExpanded: true, groupOn: ... } }
// Collapse all groups (default)features: { groupingRows: { defaultExpanded: false, groupOn: ... } }
// Expand group at index 0features: { groupingRows: { defaultExpanded: 0, groupOn: ... } }
// Expand group with specific keyfeatures: { groupingRows: { defaultExpanded: 'Engineering', groupOn: ... } }
// Expand multiple groups by keyfeatures: { groupingRows: { defaultExpanded: ['Engineering', 'Sales'], groupOn: ... } }Animation Options
Section titled “Animation Options”The animation option controls how grouped rows appear/disappear:
// Slide animation (default)features: { groupingRows: { animation: 'slide', ... } }
// Fade animationfeatures: { groupingRows: { animation: 'fade', ... } }
// No animationfeatures: { groupingRows: { animation: false, ... } }Accordion Mode
Section titled “Accordion Mode”In accordion mode, only one group can be expanded at a time. Expanding a group automatically collapses all sibling groups at the same depth level.
features: { groupingRows: { groupOn: (row) => row.department, accordion: true, // Only one group open at a time },},Aggregators
Section titled “Aggregators”<tbw-grid style="height: 400px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';
import '@toolbox-web/grid/features/grouping-rows';
// Sample data for grouping demosconst sampleData = [ { id: 1, name: 'Alice', department: 'Engineering', salary: 95000 }, { id: 2, name: 'Bob', department: 'Marketing', salary: 75000 }, { id: 3, name: 'Carol', department: 'Engineering', salary: 105000 }, { id: 4, name: 'Dan', department: 'Sales', salary: 85000 }, { id: 5, name: 'Eve', department: 'Marketing', salary: 72000 }, { id: 6, name: 'Frank', department: 'Engineering', salary: 98000 }, { id: 7, name: 'Grace', department: 'Sales', salary: 88000 },];const columns = [ { field: 'id', header: 'ID', type: 'number' }, { field: 'name', header: 'Name' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'number' },];
const grid = queryGrid('tbw-grid');
grid.gridConfig = { columns, features: { groupingRows: { groupOn: (row: { department: string }) => row.department, defaultExpanded: false, showRowCount: true, indentWidth: 20, aggregators: { salary: 'sum', }, }, }, }; grid.rows = sampleData;Display aggregate values (sum, avg, count, min, max, first, last) in group headers. Works in both full-width and per-column rendering modes.
features: { groupingRows: { groupOn: (row) => row.department, aggregators: { salary: 'sum', // Sum of salaries in each group bonus: 'avg', // Average bonus id: 'count', // Count of rows }, },},Built-in aggregators:
sum- Sum of numeric valuesavg- Average of numeric valuescount- Number of rowsmin- Minimum valuemax- Maximum valuefirst- First row’s valuelast- Last row’s value
You can also provide a custom aggregator function:
aggregators: { salary: (rows, field) => { const total = rows.reduce((sum, r) => sum + r[field], 0); return `$${total.toLocaleString()}`; },}Multi-Level Grouping
Section titled “Multi-Level Grouping”Return multiple values from groupOn to create nested group hierarchies. Row counts display correctly at every depth level.
<tbw-grid style="height: 450px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/grouping-rows';
const sampleData = [ { id: 1, name: 'Alice', country: 'USA', department: 'Engineering', salary: 95000 }, { id: 2, name: 'Bob', country: 'USA', department: 'Marketing', salary: 75000 }, { id: 3, name: 'Carol', country: 'USA', department: 'Engineering', salary: 105000 }, { id: 4, name: 'Dan', country: 'UK', department: 'Sales', salary: 85000 }, { id: 5, name: 'Eve', country: 'UK', department: 'Marketing', salary: 72000 }, { id: 6, name: 'Frank', country: 'USA', department: 'Sales', salary: 98000 }, { id: 7, name: 'Grace', country: 'UK', department: 'Sales', salary: 88000 }, { id: 8, name: 'Henry', country: 'Germany', department: 'Engineering', salary: 92000 }, { id: 9, name: 'Ivy', country: 'Germany', department: 'Engineering', salary: 110000 }, { id: 10, name: 'Jack', country: 'UK', department: 'Engineering', salary: 78000 },];
const grid = queryGrid('tbw-grid');grid.gridConfig = { columns: [ { field: 'id', header: 'ID', type: 'number', width: 60 }, { field: 'name', header: 'Name' }, { field: 'country', header: 'Country' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'number', align: 'right' }, ], features: { groupingRows: { groupOn: (row: { country: string; department: string }) => [row.country, row.department], defaultExpanded: true, showRowCount: true, }, },};grid.rows = sampleData;features: { groupingRows: { groupOn: (row) => [row.region, row.department, row.team], },},Programmatic API
Section titled “Programmatic API”See GroupingRowsPlugin for the full list of methods.
const plugin = grid.getPluginByName('groupingRows');
plugin.expand('Engineering');plugin.collapse('Engineering');plugin.toggle('Engineering');plugin.expandAll();plugin.collapseAll();plugin.isExpanded('Engineering'); // booleanplugin.getExpandedGroups(); // string[]plugin.setGroupOn((row) => [row.region, row.department]);plugin.refreshGroups();Styling
Section titled “Styling”The row grouping plugin supports CSS custom properties for theming. Override these on tbw-grid or a parent container:
CSS Custom Properties
Section titled “CSS Custom Properties”| Property | Default | Description |
|---|---|---|
--tbw-group-indent-width | 1.25em (~20px) | Indentation per group level |
--tbw-grouping-rows-bg | var(--tbw-color-panel-bg) | Group row background |
--tbw-grouping-rows-bg-hover | var(--tbw-color-row-hover) | Group row hover |
--tbw-grouping-rows-toggle-hover | var(--tbw-color-row-hover) | Toggle button hover |
--tbw-grouping-rows-count-color | var(--tbw-color-fg-muted) | Count badge color |
--tbw-grouping-rows-aggregate-color | var(--tbw-color-fg-muted) | Aggregate value color |
--tbw-toggle-size | 1.25em | Toggle button size |
--tbw-font-size-xs | 0.7857em | Count text size |
--tbw-animation-duration | 200ms | Expand/collapse animation |
--tbw-animation-easing | ease-out | Animation curve |
Example
Section titled “Example”tbw-grid { /* Custom row grouping styling */ --tbw-group-indent-width: 1.5em; /* Wider indentation */ --tbw-grouping-rows-bg: #e8f5e9; --tbw-grouping-rows-bg-hover: #c8e6c9; --tbw-grouping-rows-count-color: #388e3c;}CSS Classes
Section titled “CSS Classes”The row grouping plugin uses these class names:
| Class | Element |
|---|---|
.group-row | Group header row |
.group-toggle | Expand/collapse button |
.group-label | Group name and value |
.group-count | Row count badge |
.group-aggregates | Container for aggregate values |
.group-aggregate | Individual aggregate value |
.tbw-group-slide-in | Child row slide animation |
.tbw-group-fade-in | Child row fade animation |
[data-group-depth="N"] | Group nesting level (0-4) |
Events
Section titled “Events”Click group headers to expand/collapse groups.
| Event | Detail | Description |
|---|---|---|
group-toggle | { key, expanded, value, depth } | Fired when a group is expanded/collapsed |
group-expand | { groupKey, groupPath } | Fired when a pre-defined group is expanded |
group-collapse | { groupKey, groupPath } | Fired when a pre-defined group is collapsed |
Server-Side Data
Section titled “Server-Side Data”Requires:
ServerSidePlugin— see Server-Side Plugin
For server-side scenarios where group structure is fetched from the server and rows are loaded on demand, use ServerSidePlugin alongside Row Grouping. ServerSide returns group definitions as root data; when the user expands a group, the plugin fetches group rows via getChildRows().
import '@toolbox-web/grid/features/grouping-rows';import '@toolbox-web/grid/features/server-side';import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid('tbw-grid');grid.gridConfig = { columns: [ { field: 'name', header: 'Employee' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'currency' }, ], features: { serverSide: { pageSize: 50, dataSource: { async getRows(params) { // Return group definitions as root-level nodes const res = await fetch(`/api/groups?start=${params.startNode}&end=${params.endNode}`); const data = await res.json(); return { rows: data.groups, totalNodeCount: data.total }; }, async getChildRows(params) { // Return rows for an expanded group const { groupKey } = params.context; const res = await fetch(`/api/groups/${groupKey}/rows`); return { rows: await res.json() }; }, }, }, groupingRows: true, },};import '@toolbox-web/grid-react/features/grouping-rows';import '@toolbox-web/grid-react/features/server-side';import { DataGrid } from '@toolbox-web/grid-react';
const dataSource = { async getRows(params) { const res = await fetch(`/api/groups?start=${params.startNode}&end=${params.endNode}`); return res.json(); }, async getChildRows(params) { const { groupKey } = params.context; const res = await fetch(`/api/groups/${groupKey}/rows`); return { rows: await res.json() }; },};
function ServerGroupedGrid() { return ( <DataGrid columns={[ { field: 'name', header: 'Employee' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'currency' }, ]} serverSide={{ pageSize: 50, dataSource }} groupingRows /> );}<script setup>import '@toolbox-web/grid-vue/features/grouping-rows';import '@toolbox-web/grid-vue/features/server-side';import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';
const serverSideConfig = { pageSize: 50, dataSource: { async getRows(params) { const res = await fetch(`/api/groups?start=${params.startNode}&end=${params.endNode}`); return res.json(); }, async getChildRows(params) { const { groupKey } = params.context; const res = await fetch(`/api/groups/${groupKey}/rows`); return { rows: await res.json() }; }, },};</script>
<template> <TbwGrid :server-side="serverSideConfig" grouping-rows> <TbwGridColumn field="name" header="Employee" /> <TbwGridColumn field="department" header="Department" /> <TbwGridColumn field="salary" header="Salary" type="currency" /> </TbwGrid></template>import { GridGroupingRowsDirective } from '@toolbox-web/grid-angular/features/grouping-rows';import { GridServerSideDirective } from '@toolbox-web/grid-angular/features/server-side';import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import type { ColumnConfig } from '@toolbox-web/grid';
@Component({ selector: 'app-server-grouped-grid', imports: [Grid, GridGroupingRowsDirective, GridServerSideDirective], template: ` <tbw-grid [columns]="columns" [serverSide]="serverSideConfig" [groupingRows]="true" style="height: 400px; display: block;"> </tbw-grid> `,})export class ServerGroupedGridComponent { columns: ColumnConfig[] = [ { field: 'name', header: 'Employee' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'currency' }, ];
serverSideConfig = { pageSize: 50, dataSource: { async getRows(params: any) { const res = await fetch(`/api/groups?start=${params.startNode}&end=${params.endNode}`); return res.json(); }, async getChildRows(params: any) { const { groupKey } = params.context; const res = await fetch(`/api/groups/${groupKey}/rows`); return { rows: await res.json() }; }, }, };}Data flow:
- ServerSide fetches block → broadcasts
datasource:data - GroupingRowsPlugin claims data as pre-defined groups
- On group expand → fires
datasource:fetch-childrenwith{ source: 'grouping-rows', groupKey } - ServerSide calls
getChildRows()→ broadcastsdatasource:children - GroupingRowsPlugin receives rows and renders them under the group
Standalone Mode (Without ServerSide)
Section titled “Standalone Mode (Without ServerSide)”If ServerSidePlugin is not loaded, use the imperative API to provide groups and rows directly:
import '@toolbox-web/grid/features/grouping-rows';import { queryGrid } from '@toolbox-web/grid';
const grid = await queryGrid('tbw-grid', true);grid.gridConfig = { columns: [ { field: 'name', header: 'Employee' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'currency' }, ], features: { groupingRows: true, },};
const grouping = grid.getPluginByName('groupingRows');
// Lazy-load rows when a group is expandedgrid.on('group-expand', async ({ groupKey }) => { grouping.setGroupLoading(groupKey, true); const rows = await fetchGroupRows(groupKey); grouping.setGroupRows(groupKey, rows); // also clears loading state});
// Load groups asynchronouslyconst groups = await fetch('/api/groups').then(r => r.json());grouping.setGroups(groups);| Method | Description |
|---|---|
setGroups(groups) | Replace groups with an external structure |
getGroups() | Get the current group definitions |
setGroupRows(key, rows) | Populate rows for an expanded group |
setGroupLoading(key, loading) | Toggle loading indicator for a group |
clearGroupRows(key?) | Clear cached rows (specific group or all) |
Nested Pre-Defined Groups
Section titled “Nested Pre-Defined Groups”Groups can be nested using the children property:
const groups: GroupDefinition[] = [ { key: 'engineering', value: 'Engineering', rowCount: 150, children: [ { key: 'frontend', value: 'Frontend', rowCount: 60 }, { key: 'backend', value: 'Backend', rowCount: 90 }, ], },];Plugin Compatibility
Section titled “Plugin Compatibility”A development-mode warning is shown if incompatible plugins are loaded together.
See Also
Section titled “See Also”- Column Groups — Group column headers
- Tree — Hierarchical tree data
- Pivot — Pivot table with grouping
- Common Patterns — Application recipes using grouping
- Plugins Overview — Plugin compatibility and combinations