Skip to content

GlobalSearch Component

Modern global search experience providing quick page navigation and intelligent search suggestions. Supports keyboard shortcuts, fuzzy search, and grouped results, enabling users to quickly find and navigate to any page in the application.

View Live Demo

🔍 Smart Route Search

Automatically generates search index based on application routes without manual configuration

⚡ Keyboard Shortcuts

Support for Ctrl+K / Cmd+K shortcuts to quickly open search

📂 Grouped Results

Intelligently groups results by functional modules for quick related page location

🎯 Fuzzy Matching

Supports fuzzy matching for page titles, paths, and parent menus

⌨️ Keyboard Navigation

Complete keyboard navigation support with arrow keys for selection and Enter to confirm

🌐 Internationalization

Full support for multi-language environments with localized search results

The simplest global search component integration:

Basic Search Integration
<template>
<div>
<!-- Search trigger in header layout -->
<header class="flex items-center justify-between p-4 bg-white dark:bg-gray-800 shadow">
<div class="flex items-center gap-4">
<h1 class="text-xl font-semibold">My Application</h1>
</div>
<!-- Global Search Component -->
<GlobalSearch
trigger-class="flex items-center gap-2 px-3 py-1.5 text-sm bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
/>
<div class="flex items-center gap-3">
<!-- Other header controls -->
</div>
</header>
</div>
</template>
<script setup lang="ts">
import GlobalSearch from '@/components/GlobalSearch/index.vue'
</script>

Using programmatic approach to control the search component:

Advanced Search Control
<template>
<div>
<!-- Custom search triggers -->
<div class="search-controls">
<button
@click="openSearch"
class="search-trigger"
>
<Search class="w-4 h-4" />
<span>Search pages...</span>
<kbd class="shortcut-key">⌘K</kbd>
</button>
<!-- Additional trigger options -->
<button
@click="searchHelp"
class="help-button"
title="Search Help"
>
<HelpCircle class="w-4 h-4" />
</button>
</div>
<!-- Global Search Component -->
<GlobalSearch
v-model:open="searchOpen"
@search="handleSearch"
@select="handleSelect"
/>
<!-- Search help modal -->
<SearchHelpModal v-model:open="helpOpen" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Search, HelpCircle } from 'lucide-vue-next'
import GlobalSearch from '@/components/GlobalSearch/index.vue'
import SearchHelpModal from './components/SearchHelpModal.vue'
import type { SearchItem } from '@/hooks/useGlobalSearch'
const searchOpen = ref(false)
const helpOpen = ref(false)
// Open search
const openSearch = () => {
searchOpen.value = true
}
// Search help
const searchHelp = () => {
helpOpen.value = true
}
// Handle search query changes
const handleSearch = (query: string) => {
console.log('Search query:', query)
// Analytics can be added here
trackSearchQuery(query)
}
// Handle search result selection
const handleSelect = (item: SearchItem) => {
console.log('Selected item:', item)
// Access analytics can be added
trackPageAccess(item.path)
}
// Analytics functions (example)
const trackSearchQuery = (query: string) => {
// Send search analytics data
if (query.length >= 2) {
// analytics.track('global_search', { query })
}
}
const trackPageAccess = (path: string) => {
// Record page access
// analytics.track('page_access_via_search', { path })
}
</script>
<style>
.search-trigger {
@apply flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-700 transition-colors min-w-64;
}
.shortcut-key {
@apply ml-auto px-1.5 py-0.5 text-xs font-mono bg-white dark:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded shadow-sm;
}
.help-button {
@apply p-2 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors;
}
</style>
Custom Search Data Source
// composables/useCustomGlobalSearch.ts
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import type { SearchItem } from '@/hooks/useGlobalSearch'
export function useCustomGlobalSearch() {
const { t } = useI18n()
const searchQuery = ref('')
// Custom search data source
const customSearchItems = ref<SearchItem[]>([
// Page search
...generateRouteSearchItems(),
// Feature search
{
id: 'create-user',
name: 'create-user',
title: 'Create New User',
path: '/management/user/add',
icon: 'plus-circle',
group: 'Quick Actions'
},
{
id: 'export-data',
name: 'export-data',
title: 'Data Export',
path: '/management/user?action=export',
icon: 'download',
group: 'Quick Actions'
},
// Help content search
{
id: 'help-getting-started',
name: 'help-getting-started',
title: 'Getting Started Guide',
path: '/help/getting-started',
icon: 'book-open',
group: 'Help Center'
},
{
id: 'help-faq',
name: 'help-faq',
title: 'Frequently Asked Questions',
path: '/help/faq',
icon: 'help-circle',
group: 'Help Center'
},
// Settings shortcuts
{
id: 'settings-profile',
name: 'settings-profile',
title: 'Profile Settings',
path: '/settings/profile',
icon: 'user-circle',
group: 'Settings'
},
{
id: 'settings-theme',
name: 'settings-theme',
title: 'Theme Settings',
path: '/settings/appearance',
icon: 'palette',
group: 'Settings'
}
])
// Enhanced search logic
const filteredItems = computed(() => {
if (!searchQuery.value.trim()) {
// Show common items when no search query
return customSearchItems.value.filter(item =>
item.group === 'Quick Actions' || item.group === 'Settings'
).slice(0, 8)
}
const query = searchQuery.value.toLowerCase().trim()
return customSearchItems.value.filter(item => {
// Multi-dimensional matching
const matchTargets = [
item.title.toLowerCase(),
item.path.toLowerCase(),
item.group?.toLowerCase(),
item.parent?.toLowerCase(),
// Add keyword matching
getItemKeywords(item).join(' ').toLowerCase()
].filter(Boolean).join(' ')
return matchTargets.includes(query)
}).sort((a, b) => {
// Sort by relevance score
const aScore = calculateRelevanceScore(a, query)
const bScore = calculateRelevanceScore(b, query)
return bScore - aScore
})
})
// Calculate search relevance score
const calculateRelevanceScore = (item: SearchItem, query: string): number => {
let score = 0
const title = item.title.toLowerCase()
// Exact match priority
if (title === query) score += 100
// Title starts with match
else if (title.startsWith(query)) score += 50
// Title contains match
else if (title.includes(query)) score += 20
// Path matching
if (item.path.toLowerCase().includes(query)) score += 10
// Group matching
if (item.group?.toLowerCase().includes(query)) score += 5
return score
}
// Get item keywords
const getItemKeywords = (item: SearchItem): string[] => {
const keywords: string[] = []
// Generate keywords based on path
if (item.path.includes('/management/user')) {
keywords.push('user', 'manage', 'admin', 'account')
}
if (item.path.includes('/management/order')) {
keywords.push('order', 'manage', 'sales', 'commerce')
}
if (item.path.includes('/settings')) {
keywords.push('setting', 'config', 'preference', 'option')
}
if (item.path.includes('/help')) {
keywords.push('help', 'doc', 'guide', 'support')
}
return keywords
}
// Generate route search items (simplified example)
function generateRouteSearchItems(): SearchItem[] {
// This should generate from route configuration
return [
{
id: 'dashboard',
name: 'dashboard',
title: 'Dashboard',
path: '/dashboard',
icon: 'layout-dashboard',
group: 'Main Pages'
},
{
id: 'user-management',
name: 'user-management',
title: 'User Management',
path: '/management/user',
icon: 'users',
group: 'Management Modules'
}
// ... more route items
]
}
return {
searchQuery,
searchItems: customSearchItems,
filteredItems
}
}

The built-in useGlobalSearch composable automatically generates search data from your route configuration:

Returns:

  • searchQuery: Ref<string> - Current search input value
  • searchItems: ComputedRef<SearchItem[]> - All available search items from routes
  • filteredItems: ComputedRef<SearchItem[]> - Filtered items based on query
  • groupedItems: ComputedRef<Record<string, SearchItem[]>> - Items grouped by category

Features:

  • Auto-generates search items from route configuration
  • Supports internationalization with vue-i18n
  • Groups items by route categories (overview, management, settings)
  • Provides fuzzy matching for titles, paths, and parent routes

Suitable for management systems requiring fast page navigation and feature access.

Perfect for CMS platforms where users need to quickly locate specific content areas and editing tools.

Ideal for complex dashboard applications with multiple modules and deep navigation structures.

  • Implement search debouncing to avoid frequent calculations
  • Provide clear search suggestions and history
  • Use relevance scoring for better result ordering
  • Include loading states and progress indicators
  • Pre-compute search indexes for large datasets
  • Use virtual scrolling for handling many search results
  • Implement search result caching
  • Consider debouncing for search input
  • Provide keyboard navigation support
  • Handle empty search states gracefully
  • Add search analytics for continuous improvement
  • Include contextual help and shortcuts
High-Performance Search Implementation
// High-performance search implementation
export function useHighPerformanceSearch() {
// 1. Pre-computed search index
const searchIndex = ref<Map<string, SearchItem[]>>(new Map())
// Build search index
const buildSearchIndex = (items: SearchItem[]) => {
const index = new Map<string, SearchItem[]>()
items.forEach(item => {
// Build index for each possible search term
const searchTerms = [
...item.title.toLowerCase().split(/s+/),
...item.path.toLowerCase().split(/[/-_]/),
...(item.parent?.toLowerCase().split(/s+/) || [])
]
searchTerms.forEach(term => {
if (term.length >= 2) { // Only index terms with length >= 2
if (!index.has(term)) {
index.set(term, [])
}
index.get(term)!.push(item)
}
})
})
searchIndex.value = index
}
// 2. Fast search algorithm
const fastSearch = (query: string): SearchItem[] => {
if (!query.trim()) return []
const terms = query.toLowerCase().trim().split(/s+/)
let results: Set<SearchItem> = new Set()
// First term determines candidate set
const firstTerm = terms[0]
const candidates = searchIndex.value.get(firstTerm) || []
results = new Set(candidates)
// Subsequent terms filter results
terms.slice(1).forEach(term => {
const termResults = searchIndex.value.get(term) || []
const termSet = new Set(termResults)
// Intersection
results = new Set([...results].filter(item => termSet.has(item)))
})
return Array.from(results)
}
// 3. Search caching
const searchCache = new Map<string, SearchItem[]>()
const MAX_CACHE_SIZE = 50
const getCachedResults = (query: string): SearchItem[] | null => {
return searchCache.get(query) || null
}
const setCachedResults = (query: string, results: SearchItem[]) => {
if (searchCache.size >= MAX_CACHE_SIZE) {
// LRU cleanup strategy
const firstKey = searchCache.keys().next().value
searchCache.delete(firstKey)
}
searchCache.set(query, results)
}
return {
buildSearchIndex,
fastSearch,
getCachedResults,
setCachedResults
}
}