Grouping + aggregation
Group by department, sum salaries, average performance, expand/collapse keys.
A live, editable Svelte 5 data grid example from the SvGrid gallery (Sorting & Grouping). See the SvGrid documentation for the full API.
About this example
Row grouping in the Svelte 5 data grid over 500 people. columnGroupingFeature buckets rows by department, country or both and renders a group row in their place, rowExpandingFeature expands and collapses each group, and showGroupingControls exposes Group by this column in the header menu; buttons above the grid switch the grouping through api.setGroupBy. enableRowSummaries adds a sticky footer that sums the salary column and counts the others across every filtered row.
The grid's built-in grouping pipeline buckets rows by one or more columns and renders a group row in their place. Aggregation here is computed in the demo (the engine resolves shared values per group; this component layers sum/avg on top for the visible "Salary" and "Performance" columns via the row-summary footer).
Imports, features and API used
Imports: @svgrid/grid, ../shared/seed
Table features registered: rowSortingFeature, columnGroupingFeature, rowExpandingFeature
Columns: department (Department), country (Country), firstName (First name), lastName (Last name), age (Age), salary (Salary), performance (Performance)
SvGridApi methods called: api.setGroupBy()
Frequently asked questions
How do I group rows by a column?
Register columnGroupingFeature and rowExpandingFeature, then call api.setGroupBy(['department']) or let users pick Group by this column from the header menu with showGroupingControls on. Pass several ids for multi-level groups.
How do I show totals?
enableRowSummaries appends a sticky footer row that sums numeric columns and shows a count for the rest across every filtered row. For a rolled-up value inside each group header, set aggregate on the column, as the group aggregators demo shows.
How do I expand or collapse every group?
Click a group row to toggle it, or call api.expandAllGroups() and api.collapseAllGroups() from a toolbar. Passing an empty array to api.setGroupBy removes the grouping altogether.
Related documentation
Related articles
- Aggregation Functions Explained (Sum, Avg, Min, Max, Count) - A practical look at SvGrid's five built-in aggregations - what they compute, where the math quietly breaks, and how to handle the server-paged case where client totals are meaningless.
- Grouping and Aggregation in a Svelte Data Grid - How to group rows by one or more columns and show per-group sums, averages, and counts using SvGrid's columnGroupingFeature - with real code and the edge cases that actually bite.
- Building a Project / Task Board with a Svelte Data Grid - How to build a task management grid with grouping by status or assignee, inline edits, subtask tree rows, and saved views - without reaching for a dedicated project management tool.
Source code (07-grouping-aggregation.svelte)
<script lang="ts">
/**
* 07. Grouping + aggregation
* --------------------------
* The grid's built-in grouping pipeline buckets rows by one or more
* columns and renders a group row in their place. Aggregation here is
* computed in the demo (the engine resolves shared values per group; this
* component layers sum/avg on top for the visible "Salary" and
* "Performance" columns via the row-summary footer).
*/
import {
SvGrid,
tableFeatures,
rowSortingFeature,
columnGroupingFeature,
rowExpandingFeature,
type GridColumns,
type SvGridApi,
} from '@svgrid/grid'
import { makePeople, type Person } from '../shared/seed'
const features = tableFeatures({
rowSortingFeature,
columnGroupingFeature,
rowExpandingFeature,
})
const rows = makePeople(500)
const columns: GridColumns<Person> = [
{ field: 'department', header: 'Department', editorType: 'text' },
{ field: 'country', header: 'Country', editorType: 'text' },
{ field: 'firstName', header: 'First name', editorType: 'text' },
{ field: 'lastName', header: 'Last name', editorType: 'text' },
{ field: 'age', header: 'Age', editorType: 'number' },
{
field: 'salary',
header: 'Salary',
editorType: 'number',
format: { type: 'currency', currency: 'USD', options: { maximumFractionDigits: 0 } },
},
{ field: 'performance', header: 'Performance', editorType: 'number' },
]
let api = $state<SvGridApi<typeof features, Person> | null>(null)
let groupBy = $state<string[]>(['department'])
function applyGroup(by: string[]) {
groupBy = by
api?.setGroupBy(by)
}
</script>
<section class="flex flex-col flex-1 min-h-0 gap-3">
<div class="flex flex-wrap items-center gap-2 text-sm shrink-0">
<span class="font-medium">Group by:</span>
<button
onclick={() => applyGroup([])}
class="gb-btn rounded border px-3 py-1 {groupBy.length === 0 ? 'is-on' : ''}"
>None</button>
<button
onclick={() => applyGroup(['department'])}
class="gb-btn rounded border px-3 py-1 {groupBy.join() === 'department' ? 'is-on' : ''}"
>Department</button>
<button
onclick={() => applyGroup(['country'])}
class="gb-btn rounded border px-3 py-1 {groupBy.join() === 'country' ? 'is-on' : ''}"
>Country</button>
<button
onclick={() => applyGroup(['department', 'country'])}
class="gb-btn rounded border px-3 py-1 {groupBy.join() === 'department,country' ? 'is-on' : ''}"
>Department → Country</button>
<span class="gb-hint ml-3">
Click a group row to expand. The row-summaries footer aggregates totals.
</span>
</div>
<div class="flex-1 min-h-0">
<SvGrid responsive={true}
columnResize
data={rows}
columns={columns}
features={features}
filterMode="menu"
selectionMode="cell"
showGroupingControls={true}
enableInlineEditing={false}
enableCellSelection={true}
enableRowSummaries={true}
rowHeight={36}
containerHeight="100%"
fitColumns={true}
onApiReady={(next) => {
api = next
// Apply the initial group-by once the imperative API is available,
// then auto-expand the first group so the data isn't hidden behind a
// single closed row on first paint.
next.setGroupBy(groupBy)
queueMicrotask(() => next.setRowExpanded(`department:Engineering`, true))
}}
/>
</div>
</section>
<style>
.gb-btn {
border-color: var(--sg-border, #cbd5e1);
background: var(--sg-bg, #fff);
color: var(--sg-fg, #0f172a);
}
.gb-btn:hover { background: var(--sg-row-hover-bg, rgba(148, 163, 184, 0.12)); }
.gb-btn.is-on {
background: var(--sg-bg-subtle, var(--sg-header-bg, #e2e8f0));
border-color: var(--sg-border, #cbd5e1);
}
.gb-hint { color: var(--sg-muted, #64748b); }
</style>More Sorting & Grouping examples
- Group aggregators - Declarative per-column rollups for group rows via the aggregate column option: sum, avg, min, max, count, countDistinct, extent, first, or a custom (values, rows) reducer. Each rollup is formatted with the column format and shown in the group header.
- Group panel (drag & drop) - A Group Panel: drag chips into the panel to group, drag inside to reorder grouping levels, × to ungroup. Drives api.setGroupBy() under the hood.
- Group display modes + footers - Switch between groupRows banners, a single combined Group column, and one column per grouped field. groupFooters closes each group with a subtotal row under the real columns. Paging counts DATA rows, so pageSize means what it says: a page reprints the banners its rows sit under, and footers never eat the budget.
- Tree data (hierarchy) - treeData nests rows by parent id into an expandable hierarchy. Tree rows stay real data rows - own cells, formatting, editing - and just gain an expander plus indent. Takes flat parent-id data directly, or nested children arrays via flattenTreeData. Full treegrid a11y with arrow-key expand.
- Reporting workspace - Pivot-lite: group-by chips, per-column aggregator picker, saved views with localStorage persistence, live KPI strip + summary cards.