NuGrid provides built-in sorting and filtering capabilities powered by TanStack Table.
Click on the Name column header to toggle sorting.
<script setup lang="ts">
import type { NuGridColumn } from '#nu-grid/types'
interface User {
id: number
name: string
email: string
age: number
}
const data = ref<User[]>([
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', age: 28 },
{ id: 2, name: 'Bob Smith', email: 'bob@example.com', age: 35 },
{ id: 3, name: 'Carol White', email: 'carol@example.com', age: 42 },
{ id: 4, name: 'David Brown', email: 'david@example.com', age: 31 },
{ id: 5, name: 'Emma Davis', email: 'emma@example.com', age: 26 },
])
const UButton = resolveComponent('UButton')
const columns: NuGridColumn<User>[] = [
{ accessorKey: 'id', header: 'ID', size: 60, enableSorting: true },
{
accessorKey: 'name',
header: ({ column }) => {
const isSorted = column.getIsSorted()
return h(UButton, {
color: 'neutral',
variant: 'ghost',
label: 'Name',
icon: isSorted
? isSorted === 'asc'
? 'i-lucide-arrow-up-narrow-wide'
: 'i-lucide-arrow-down-wide-narrow'
: 'i-lucide-arrow-up-down',
class: '-mx-2.5',
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
})
},
size: 150,
enableSorting: true,
},
{ accessorKey: 'email', header: 'Email', size: 200, enableSorting: true },
{ accessorKey: 'age', header: 'Age', size: 80, enableSorting: true },
]
const sorting = ref([{ id: 'name', desc: false }])
</script>
<template>
<div class="w-full">
<p class="mb-3 text-sm text-muted">Click on the Name column header to toggle sorting.</p>
<NuGrid
v-model:sorting="sorting"
:data="data"
:columns="columns"
: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>
Sorting is enabled by default. Click column headers to sort:
<template>
<NuGrid :data="data" :columns="columns" />
</template>
Track and control sort state with v-model:
<script setup lang="ts">
import type { SortingState } from '@tanstack/vue-table'
const sorting = ref<SortingState>([])
// Pre-sort by name ascending
const sorting = ref<SortingState>([
{ id: 'name', desc: false }
])
</script>
<template>
<NuGrid
v-model:sorting="sorting"
:data="data"
:columns="columns"
/>
</template>
Hold Shift and click to sort by multiple columns:
<template>
<NuGrid
v-model:sorting="sorting"
:data="data"
:columns="columns"
/>
</template>
const columns: NuGridColumn<User>[] = [
{
accessorKey: 'actions',
header: 'Actions',
enableSorting: false, // Disable sorting for this column
},
]
Provide a custom sort function for complex sorting:
const columns: NuGridColumn<User>[] = [
{
accessorKey: 'status',
header: 'Status',
sortingFn: (rowA, rowB, columnId) => {
const order = { active: 0, pending: 1, inactive: 2 }
return order[rowA.original.status] - order[rowB.original.status]
},
},
]
Listen to sort changes:
<script setup lang="ts">
function onSortChanged(event) {
console.log('Sort changed:', event.sorting)
}
</script>
<template>
<NuGrid
:data="data"
:columns="columns"
@sort-changed="onSortChanged"
/>
</template>
Set up column filters:
<script setup lang="ts">
import type { ColumnFiltersState } from '@tanstack/vue-table'
const columnFilters = ref<ColumnFiltersState>([])
</script>
<template>
<NuGrid
v-model:column-filters="columnFilters"
:data="data"
:columns="columns"
/>
</template>
Build your own filter UI:
<script setup lang="ts">
const gridRef = useTemplateRef('grid')
const emailFilter = ref('')
watch(emailFilter, (value) => {
gridRef.value?.tableApi?.getColumn('email')?.setFilterValue(value)
})
</script>
<template>
<div class="mb-4">
<UInput
v-model="emailFilter"
placeholder="Filter by email..."
icon="i-lucide-search"
/>
</div>
<NuGrid
ref="grid"
v-model:column-filters="columnFilters"
:data="data"
:columns="columns"
/>
</template>
Specify the filter function per column:
const columns: NuGridColumn<User>[] = [
{
accessorKey: 'status',
header: 'Status',
filterFn: 'equals', // Exact match filter
},
{
accessorKey: 'name',
header: 'Name',
filterFn: 'includesString', // Contains filter (default)
},
]
| Function | Description |
|---|---|
includesString | Case-insensitive contains |
includesStringSensitive | Case-sensitive contains |
equalsString | Case-insensitive equals |
equals | Strict equality |
arrIncludes | Value in array |
arrIncludesAll | All values in array |
arrIncludesSome | Some values in array |
weakEquals | Loose equality |
inNumberRange | Within numeric range |
const columns: NuGridColumn<User>[] = [
{
accessorKey: 'salary',
header: 'Salary',
filterFn: (row, columnId, filterValue) => {
const salary = row.getValue(columnId) as number
const [min, max] = filterValue as [number, number]
return salary >= min && salary <= max
},
},
]
<script setup lang="ts">
function onFilterChanged(event) {
console.log('Filters changed:', event.columnFilters)
}
</script>
<template>
<NuGrid
:data="data"
:columns="columns"
@filter-changed="onFilterChanged"
/>
</template>
Filter across all columns:
<script setup lang="ts">
const globalFilter = ref('')
</script>
<template>
<div class="mb-4">
<UInput
v-model="globalFilter"
placeholder="Search all columns..."
icon="i-lucide-search"
/>
</div>
<NuGrid
v-model:global-filter="globalFilter"
:data="data"
:columns="columns"
/>
</template>
Build a complete filter toolbar:
<script setup lang="ts">
const gridRef = useTemplateRef('grid')
const searchQuery = ref('')
const statusFilter = ref('all')
const statusOptions = [
{ label: 'All', value: 'all' },
{ label: 'Active', value: 'active' },
{ label: 'Pending', value: 'pending' },
{ label: 'Inactive', value: 'inactive' },
]
watch(statusFilter, (value) => {
const statusColumn = gridRef.value?.tableApi?.getColumn('status')
if (value === 'all') {
statusColumn?.setFilterValue(undefined)
} else {
statusColumn?.setFilterValue(value)
}
})
</script>
<template>
<div class="mb-4 flex gap-4">
<UInput
v-model="searchQuery"
placeholder="Search by email..."
icon="i-lucide-search"
class="w-64"
@update:model-value="gridRef?.tableApi?.getColumn('email')?.setFilterValue($event)"
/>
<USelect
v-model="statusFilter"
:items="statusOptions"
placeholder="Filter status"
class="w-40"
/>
<UButton
color="neutral"
variant="outline"
@click="columnFilters = []; statusFilter = 'all'; searchQuery = ''"
>
Clear Filters
</UButton>
</div>
<NuGrid
ref="grid"
v-model:column-filters="columnFilters"
:data="data"
:columns="columns"
/>
</template>
For server-side operations, disable client-side processing:
<script setup lang="ts">
const sorting = ref([])
const columnFilters = ref([])
// Watch for changes and fetch from server
watch([sorting, columnFilters], async () => {
await fetchData({
sorting: sorting.value,
filters: columnFilters.value,
})
}, { deep: true })
</script>
<template>
<NuGrid
v-model:sorting="sorting"
v-model:column-filters="columnFilters"
:data="data"
:columns="columns"
manual-sorting
manual-filtering
/>
</template>