Layout & Display

Grouping

Group rows by columns in NuGrid.

NuGrid supports grouping rows by one or more columns, creating expandable hierarchical views of your data.

Department
ID
Name
Role
Salary
Department: Engineering (3 items)
Engineering
1
Alice
Developer
$95,000
Engineering
3
Carol
Lead
$110,000
Engineering
5
Emma
Developer
$90,000
Department: Marketing (2 items)
Marketing
2
Bob
Manager
$85,000
Marketing
6
Frank
Designer
$75,000
Department: HR (2 items)
HR
4
David
Specialist
$60,000
HR
7
Grace
Manager
$80,000
<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>

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>

Group Aggregations

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
  },
]

Available Aggregation Functions

FunctionDescription
sumSum of values
minMinimum value
maxMaximum value
meanAverage value
medianMedian value
countNumber of rows
uniqueUnique values
uniqueCountCount of unique values
extentmin, max array

Custom Aggregation

{
  accessorKey: 'status',
  header: 'Status',
  aggregationFn: (columnId, leafRows, childRows) => {
    const active = leafRows.filter(r => r.original.status === 'active').length
    return `${active}/${leafRows.length} active`
  },
}

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:row-selection="rowSelection"
    :data="data"
    :columns="columns"
    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>

Next Steps

Virtualization

Handle large datasets efficiently.

Column Sizing

Resize and auto-size columns.