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>
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>
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.
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>
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>
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>
Display aggregate values in group rows:
const columns: NuGridColumn<Product>[] = [
{
accessorKey: 'price',
header: 'Price',
aggregationFn: 'sum', // Sum prices in group
aggregatedCell: ({ getValue }) => {
return `Total: $${getValue().toFixed(2)}`
},
},
{
accessorKey: 'quantity',
header: 'Quantity',
aggregationFn: 'sum',
},
{
accessorKey: 'rating',
header: 'Rating',
aggregationFn: 'mean', // Average rating
},
]
| Function | Description |
|---|---|
sum | Sum of values |
min | Minimum value |
max | Maximum value |
mean | Average value |
median | Median value |
count | Number of rows |
unique | Unique values |
uniqueCount | Count of unique values |
extent | min, max array |
{
accessorKey: 'status',
header: 'Status',
aggregationFn: (columnId, leafRows, childRows) => {
const active = leafRows.filter(r => r.original.status === 'active').length
return `${active}/${leafRows.length} active`
},
}
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()
},
},
]
Row selection works with grouping:
<template>
<NuGrid
v-model:grouping="grouping"
v-model:row-selection="rowSelection"
:data="data"
:columns="columns"
selection="multi"
/>
</template>
Selecting a group row selects all rows within that group.
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.
<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>
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>