Essentials

Sorting, Filtering & Search

Sort, filter, and search data in NuGrid.

NuGrid provides built-in sorting, filtering, and search capabilities.

Click on the Name column header to toggle sorting.

ID
Email
Age
1
Alice Johnson
alice@example.com
28
2
Bob Smith
bob@example.com
35
3
Carol White
carol@example.com
42
4
David Brown
david@example.com
31
5
Emma Davis
emma@example.com
26
<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

FunctionDescription
includesStringCase-insensitive contains
includesStringSensitiveCase-sensitive contains
equalsStringCase-insensitive equals
equalsStrict equality
arrIncludesValue in array
arrIncludesAllAll values in array
arrIncludesSomeSome values in array
weakEqualsLoose equality
inNumberRangeWithin 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>

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

OptionTypeDefaultDescription
enabledbooleanfalseEnable search functionality
placeholderstring'Search...'Input placeholder text
debouncenumber300Debounce delay in milliseconds
autofocusbooleanfalseAuto-focus input on mount
clearablebooleantrueShow clear button
iconstring'i-lucide-search'Input icon
suppressPanelbooleanfalseHide built-in panel (use external controls)
typeToSearchbooleantrueStart search by typing when grid is focused
focusOnResultsbooleanfalseAuto-focus first matching cell when results change
highlightColorstring'primary'Highlight color for matches: 'primary', 'yellow', 'green', 'blue', 'orange', 'red', or custom CSS classes

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.

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

PropTypeDefaultDescription
modelValuestring-v-model for search query
placeholderstring'Search...'Input placeholder
debouncenumber300Debounce delay in ms
autofocusbooleanfalseAuto-focus on mount
clearablebooleantrueShow clear button
iconstring'i-lucide-search'Input icon
size'xs' | 'sm' | 'md' | 'lg' | 'xl''sm'Input size
loadingbooleanfalseShow loading state
disabledbooleanfalseDisable 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>

Next Steps

Events

Learn about all grid events.

Cell Editing

Enable inline cell editing.