Grouping
NuGrid supports grouping rows by one or more columns, creating expandable hierarchical views of your data.
<script setup lang="ts">
import type { NuGridColumn } from '#nu-grid/types'
interface Employee {
id: number
name: string
department: string
role: string
salary: number
}
const data = ref<Employee[]>([
{ id: 1, name: 'Alice', department: 'Engineering', role: 'Developer', salary: 95000 },
{ id: 2, name: 'Bob', department: 'Marketing', role: 'Manager', salary: 85000 },
{ id: 3, name: 'Carol', department: 'Engineering', role: 'Lead', salary: 110000 },
{ id: 4, name: 'David', department: 'HR', role: 'Specialist', salary: 60000 },
{ id: 5, name: 'Emma', department: 'Engineering', role: 'Developer', salary: 90000 },
{ id: 6, name: 'Frank', department: 'Marketing', role: 'Designer', salary: 75000 },
{ id: 7, name: 'Grace', department: 'HR', role: 'Manager', salary: 80000 },
])
const columns: NuGridColumn<Employee>[] = [
{ accessorKey: 'id', header: 'ID', size: 60 },
{ accessorKey: 'name', header: 'Name', size: 120 },
{ accessorKey: 'department', header: 'Department', size: 120 },
{ accessorKey: 'role', header: 'Role', size: 120 },
{
accessorKey: 'salary',
header: 'Salary',
size: 100,
cell: ({ row }) => `$${row.original.salary.toLocaleString()}`,
},
]
</script>
<template>
<div class="w-full">
<NuGrid
:data="data"
:columns="columns"
:grouping="['department']"
:layout="{ mode: 'group' }"
:ui="{
base: 'w-full border-separate border-spacing-0',
thead: '[&>tr]:bg-elevated/50',
th: 'py-2 border-y border-default first:border-l last:border-r first:rounded-l-lg last:rounded-r-lg',
td: 'border-b border-default',
}"
/>
</div>
</template>
Basic Grouping
Enable grouping with the grouping v-model:
<script setup lang="ts">
const grouping = ref(['category'])
</script>
<template>
<NuGrid
v-model:grouping="grouping"
:data="data"
:columns="columns"
/>
</template>
Groups render in the 'group' (or 'splitgroup') layout. A grid that starts with grouping set and
no layout.mode uses 'group' automatically. If grouping can start empty and be switched on later,
set :layout="{ mode: 'group' }" yourself: the layout is chosen when the grid is created, and a flat
('div') grid shows grouped rows ungrouped, with a warning in development. Until grouping is set, a
'group' or 'splitgroup' grid shows its rows flat, under one set of column headers.
Multi-Level Grouping
Group by multiple columns:
<script setup lang="ts">
const grouping = ref(['category', 'status'])
</script>
<template>
<NuGrid
v-model:grouping="grouping"
:data="data"
:columns="columns"
/>
</template>
Rows will be grouped first by category, then by status within each category.
Dynamic Grouping
Allow users to change grouping:
<script setup lang="ts">
const grouping = ref<string[]>([])
const availableGroups = ['category', 'status', 'assignee']
</script>
<template>
<div class="mb-4 flex gap-2">
<UButton
v-for="col in availableGroups"
:key="col"
:color="grouping.includes(col) ? 'primary' : 'neutral'"
:variant="grouping.includes(col) ? 'solid' : 'outline'"
@click="
grouping.includes(col)
? grouping = grouping.filter(g => g !== col)
: grouping = [...grouping, col]
"
>
{{ col }}
</UButton>
<UButton
v-if="grouping.length"
color="neutral"
variant="ghost"
@click="grouping = []"
>
Clear
</UButton>
</div>
<NuGrid
v-model:grouping="grouping"
:data="data"
:columns="columns"
/>
</template>
Expand/Collapse State
Track and control expanded groups:
<script setup lang="ts">
const expanded = ref<Record<string, boolean>>({})
</script>
<template>
<NuGrid
v-model:grouping="grouping"
v-model:expanded="expanded"
:data="data"
:columns="columns"
/>
<div class="mt-4">
<UButton @click="expanded = {}">Collapse All</UButton>
</div>
</template>
Programmatic Expand/Collapse
Use the grid ref to control expansion:
<script setup lang="ts">
const gridRef = useTemplateRef('grid')
function expandAll() {
gridRef.value?.tableApi?.toggleAllRowsExpanded(true)
}
function collapseAll() {
gridRef.value?.tableApi?.toggleAllRowsExpanded(false)
}
function toggleGroup(groupId: string) {
gridRef.value?.tableApi?.getRow(groupId)?.toggleExpanded()
}
</script>
<template>
<div class="mb-4 flex gap-2">
<UButton @click="expandAll">Expand All</UButton>
<UButton @click="collapseAll">Collapse All</UButton>
</div>
<NuGrid
ref="grid"
v-model:grouping="grouping"
:data="data"
:columns="columns"
/>
</template>
Summaries & Aggregates
NuGrid provides a built-in summary system for calculating aggregates across groups and displaying grand totals.
Enabling Summaries
Use the summaries prop to enable summary features:
<template>
<NuGrid
v-model:grouping="grouping"
:data="data"
:columns="columns"
:summaries="{
grandTotals: { label: 'Grand Total' },
groupSummaries: true,
}"
/>
</template>
Summaries Prop Options
| Option | Type | Default | Description |
|---|---|---|---|
grandTotals | boolean | { enabled?, label?, position? } | false | Show grand totals footer row |
groupSummaries | boolean | true | Show summary values in collapsed group headers |
Column Summary Configuration
Define aggregates at the column level using the summary property:
const columns: NuGridColumn<Product>[] = [
{
accessorKey: 'price',
header: 'Price',
summary: {
aggregate: 'sum',
format: (v) => `$${v.toFixed(2)}`,
},
},
{
accessorKey: 'quantity',
header: 'Quantity',
summary: { aggregate: 'sum' },
},
{
accessorKey: 'rating',
header: 'Rating',
summary: {
aggregate: 'avg',
format: (v) => `${v.toFixed(1)} ★`,
},
},
]
Built-in Aggregates
| Aggregate | Description |
|---|---|
sum | Sum of all values |
avg | Average of all values |
count | Count of rows |
min | Minimum value |
max | Maximum value |
Custom Aggregate Function
{
accessorKey: 'status',
header: 'Status',
summary: {
aggregate: (rows) => {
const active = rows.filter(r => r.status === 'active').length
return `${active}/${rows.length} active`
},
},
}
Split Group Mode Behavior
In splitgroup layout mode:
- Collapsed groups show summary values in a compact header row below the group header
- When group summaries are disabled, the column headers are hidden for collapsed groups, showing only the group header
<template>
<NuGrid
v-model:grouping="grouping"
:data="data"
:columns="columns"
:layout="{ mode: 'splitgroup' }"
:summaries="{ grandTotals: true, groupSummaries: true }"
/>
</template>
Grand Totals
The grand totals row appears at the bottom of the grid and shows aggregate values across all data:
<template>
<NuGrid
:data="data"
:columns="columns"
:summaries="{
grandTotals: {
enabled: true,
label: 'Total',
position: 'bottom', // 'top' or 'bottom'
},
}"
/>
</template>
Group Row Rendering
Customize group row appearance:
const columns: NuGridColumn<Product>[] = [
{
accessorKey: 'category',
header: 'Category',
cell: ({ row, getValue }) => {
if (row.getIsGrouped()) {
return h('div', { class: 'flex items-center gap-2' }, [
h(UIcon, {
name: row.getIsExpanded()
? 'i-lucide-chevron-down'
: 'i-lucide-chevron-right',
}),
h('span', { class: 'font-semibold' }, getValue()),
h('span', { class: 'text-muted' }, `(${row.subRows.length})`),
])
}
return getValue()
},
},
]
Grouping with Selection
Row selection works with grouping:
<template>
<NuGrid
v-model:grouping="grouping"
v-model:selected-rows="selectedRows"
:data="data"
:columns="columns"
row-selection="multi"
/>
</template>
Selecting a group row selects all rows within that group.
Grouping with Pagination
Pagination and grouping can be combined:
<template>
<NuGrid
v-model:grouping="grouping"
:data="data"
:columns="columns"
:paging="{ pageSize: 20 }"
/>
</template>
Groups that span multiple pages will show partial content on each page.
Nested Groups Example
<script setup lang="ts">
interface Task {
id: number
name: string
project: string
status: string
priority: string
assignee: string
}
const data = ref<Task[]>([
{ id: 1, name: 'Task 1', project: 'Alpha', status: 'active', priority: 'high', assignee: 'Alice' },
{ id: 2, name: 'Task 2', project: 'Alpha', status: 'active', priority: 'low', assignee: 'Bob' },
{ id: 3, name: 'Task 3', project: 'Beta', status: 'pending', priority: 'high', assignee: 'Alice' },
// ...
])
const grouping = ref(['project', 'status'])
const columns: NuGridColumn<Task>[] = [
{ accessorKey: 'project', header: 'Project' },
{ accessorKey: 'status', header: 'Status' },
{ accessorKey: 'name', header: 'Task' },
{ accessorKey: 'priority', header: 'Priority' },
{ accessorKey: 'assignee', header: 'Assignee' },
]
</script>
<template>
<NuGrid
v-model:grouping="grouping"
:data="data"
:columns="columns"
/>
</template>
Styling Groups
Group rows receive special attributes for styling:
<template>
<NuGrid
v-model:grouping="grouping"
:data="data"
:columns="columns"
:ui="{
tbody: '[&>tr[data-grouped=true]]:bg-elevated/30 [&>tr[data-grouped=true]]:font-semibold',
}"
/>
</template>