Sorting, Filtering & Search
NuGrid provides built-in sorting, filtering, and search capabilities.
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="text-muted mb-3 text-sm">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
Basic Sorting
Sorting is enabled by default. Click column headers to sort:
<template>
<NuGrid :data="data" :columns="columns" />
</template>
Sort State
Track and control sort state with v-model:
<script setup lang="ts">
import type { SortingState } from '#nu-grid/types'
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>
Multi-Column Sorting
Hold Shift and click to sort by multiple columns:
<template>
<NuGrid
v-model:sorting="sorting"
:data="data"
:columns="columns"
/>
</template>
Disable Sorting Per Column
const columns: NuGridColumn<User>[] = [
{
accessorKey: 'actions',
header: 'Actions',
enableSorting: false, // Disable sorting for this column
},
]
Custom Sort Function
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]
},
},
]
Sort Changed Event
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>
Filtering
Column Filters
Set up column filters:
<script setup lang="ts">
import type { ColumnFiltersState } from '#nu-grid/types'
const columnFilters = ref<ColumnFiltersState>([])
</script>
<template>
<NuGrid
v-model:column-filters="columnFilters"
:data="data"
:columns="columns"
/>
</template>
External Filter Controls
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>
Filter Functions
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)
},
]
Available Filter Functions
| 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 |
Custom Filter Function
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
},
},
]
Filter Changed Event
<script setup lang="ts">
function onFilterChanged(event) {
console.log('Filters changed:', event.columnFilters)
}
</script>
<template>
<NuGrid
:data="data"
:columns="columns"
@filter-changed="onFilterChanged"
/>
</template>
Global Filter
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>
Search
NuGrid provides a built-in search-as-you-type feature with debounced input, match highlighting, and type-to-search support.
Enable Search Panel
The simplest way to add search is with the built-in search panel:
<template>
<!-- Enable with defaults -->
<NuGrid
:data="data"
:columns="columns"
:search="true"
/>
<!-- Or with options -->
<NuGrid
:data="data"
:columns="columns"
:search="{
enabled: true,
placeholder: 'Search products...',
debounce: 300,
autofocus: true
}"
/>
</template>
Search Options
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable search functionality |
placeholder | string | 'Search...' | Input placeholder text |
debounce | number | 300 | Debounce delay in milliseconds |
autofocus | boolean | false | Auto-focus input on mount |
clearable | boolean | true | Show clear button |
icon | string | 'i-lucide-search' | Input icon |
suppressPanel | boolean | false | Hide built-in panel (use external controls) |
typeToSearch | boolean | true | Start search by typing when grid is focused |
focusOnResults | boolean | false | Auto-focus first matching cell when results change |
highlightColor | string | 'primary' | Highlight color for matches: 'primary', 'yellow', 'green', 'blue', 'orange', 'red', or custom CSS classes |
Type-to-Search
When enabled (default), users can start searching by simply clicking the grid and typing. The grid captures keystrokes when:
- The grid has focus
- No cell is being edited
- A printable character is pressed
<template>
<!-- Type-to-search is enabled by default when search is enabled -->
<NuGrid
:data="data"
:columns="columns"
:search="{ enabled: true }"
/>
<!-- Disable type-to-search if needed -->
<NuGrid
:data="data"
:columns="columns"
:search="{ enabled: true, typeToSearch: false }"
/>
</template>
Match Highlighting
When search is active, matching text is automatically highlighted in cells. By default, highlights use your app's primary color. You can customize this with the highlightColor option:
<template>
<!-- Use primary color (default) -->
<NuGrid :search="{ enabled: true }" />
<!-- Use yellow highlight -->
<NuGrid :search="{ enabled: true, highlightColor: 'yellow' }" />
<!-- Use custom Tailwind classes -->
<NuGrid :search="{ enabled: true, highlightColor: 'bg-pink-200 dark:bg-pink-500/30 rounded px-1' }" />
</template>
Available preset colors: 'primary' (default), 'yellow', 'green', 'blue', 'orange', 'red'. You can also provide custom Tailwind classes for full control over the styling.
Exclude Columns from Search
Use enableSearching: false on columns you want to exclude:
const columns: NuGridColumn<Product>[] = [
{ accessorKey: 'name', header: 'Name' }, // Searchable
{ accessorKey: 'description', header: 'Description' }, // Searchable
{ accessorKey: 'internalId', enableSearching: false }, // Excluded
{ accessorKey: 'sku', enableSearching: false }, // Excluded
]
Programmatic Control
Access search functionality via the grid ref:
<script setup lang="ts">
const gridRef = useTemplateRef('grid')
function focusSearch() {
gridRef.value?.searchFocus()
}
function setSearch(query: string) {
gridRef.value?.searchSetQuery(query)
}
function clearSearch() {
gridRef.value?.searchClear()
}
</script>
<template>
<div class="mb-4 flex gap-2">
<UButton @click="focusSearch">Focus Search</UButton>
<UButton @click="setSearch('Apple')">Search "Apple"</UButton>
<UButton @click="clearSearch">Clear</UButton>
</div>
<NuGrid
ref="grid"
:data="data"
:columns="columns"
:search="{ enabled: true }"
/>
</template>
Standalone Search Component
For custom layouts, use the NuGridSearch component with v-model:global-filter:
<script setup lang="ts">
const searchQuery = ref('')
</script>
<template>
<div class="mb-4">
<NuGridSearch
v-model="searchQuery"
placeholder="Filter products..."
:debounce="300"
/>
</div>
<NuGrid
v-model:global-filter="searchQuery"
:data="data"
:columns="columns"
/>
</template>
NuGridSearch Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | - | v-model for search query |
placeholder | string | 'Search...' | Input placeholder |
debounce | number | 300 | Debounce delay in ms |
autofocus | boolean | false | Auto-focus on mount |
clearable | boolean | true | Show clear button |
icon | string | 'i-lucide-search' | Input icon |
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'sm' | Input size |
loading | boolean | false | Show loading state |
disabled | boolean | false | Disable input |
Theming
Search-related theme slots:
const customTheme = {
slots: {
// Search panel container
searchPanel: 'flex items-center gap-2 px-4 py-2 border-b border-default bg-elevated',
// Search input wrapper
searchInput: 'w-full min-w-[200px] max-w-sm',
// Match highlight styling
searchHighlight: 'bg-yellow-200 dark:bg-yellow-500/30 rounded-sm px-0.5 -mx-0.5',
},
}
Example: Filter Toolbar
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>
Server-Side Sorting & Filtering
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>