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 '@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], 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>) { grid.gridConfig = { columns, features: { groupingRows: { animation: (opts.animation as string) ?? 'slide', groupOn: (row: { department: string }) => row.department, defaultExpanded: opts.defaultExpanded as boolean ?? false, showRowCount: opts.showRowCount as boolean ?? true, indentWidth: (opts.indentWidth as number) ?? 20, fullWidth: true, accordion: opts.accordion as boolean ?? false, } as any, }, }; grid.rows = sampleData;}
rebuild({ animation: 'slide', defaultExpanded: false, showRowCount: true, accordion: false, indentWidth: 20 });Expanded by Default
Section titled “Expanded by Default”<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: true, showRowCount: true, indentWidth: 20, }, }, }; grid.rows = sampleData;Without Row Count
Section titled “Without Row Count”<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: false, indentWidth: 20, }, }, }; grid.rows = sampleData;Expanded by Key
Section titled “Expanded by Key”<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 grid = queryGrid('tbw-grid');
const sampleData = [ { id: 1, name: 'Alice Johnson', department: 'Engineering', salary: 95000 }, { id: 2, name: 'Bob Smith', department: 'Marketing', salary: 75000 }, { id: 3, name: 'Carol Williams', department: 'Engineering', salary: 105000 }, { id: 4, name: 'Dan Brown', department: 'Sales', salary: 85000 }, { id: 5, name: 'Eve Davis', department: 'Marketing', salary: 72000 }, { id: 6, name: 'Frank Miller', department: 'Engineering', salary: 98000 }, { id: 7, name: 'Grace Lee', department: 'Sales', salary: 82000 }, { id: 8, name: 'Henry Wilson', department: 'HR', salary: 68000 }, { id: 9, name: 'Ivy Chen', department: 'Engineering', salary: 112000 }, { id: 10, name: 'Jack Taylor', department: 'Marketing', salary: 78000 },];
grid.gridConfig = { columns: [ { field: 'id', header: 'ID', type: 'number', width: 60 }, { field: 'name', header: 'Name' }, { field: 'department', header: 'Department' }, { field: 'salary', header: 'Salary', type: 'number', align: 'right' }, ], features: { groupingRows: { groupOn: (row: { department: string }) => row.department, defaultExpanded: 'Engineering', showRowCount: true, } as any, },};grid.rows = sampleData;Pass a specific key string to defaultExpanded to expand only that group initially.
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”<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, accordion: true, defaultExpanded: false, showRowCount: true, indentWidth: 20, }, }, }; grid.rows = sampleData;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:
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 |
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