UISearch
UISearch
A versatile search component that provides real-time search suggestions, filtering options, and keyboard navigation support.
<template> <UISearch v-model="searchQuery" :placeholder="'Search items...'" :loading="isLoading" :suggestions="filteredItems" :debounce="300" @search="handleSearch" @select="handleSelect" > <template #suggestion="{ item }"> <div class="flex items-center space-x-3 p-2"> <SearchIcon class="w-5 h-5 text-gray-400" /> <div> <div class="font-medium">{{ item.title }}</div> <div class="text-sm text-gray-500"> {{ item.description }} </div> </div> </div> </template> </UISearch></template>
<script setup lang="ts">import { SearchIcon } from '@gohighlevel/ghl-icons/24/outline'
interface SearchItem { id: string title: string description: string}
const searchQuery = ref('')const isLoading = ref(false)const items = ref<SearchItem[]>([])
const filteredItems = computed(() => items.value.filter(item => item.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ))
const handleSearch = async (query: string) => { isLoading.value = true try { // Perform search operation const results = await searchItems(query) items.value = results } finally { isLoading.value = false }}
const handleSelect = (item: SearchItem) => { // Handle item selection console.log('Selected:', item)}</script>Props
modelValue(string): Current search queryplaceholder(string): Input placeholder textsuggestions(array): Search suggestionsloading(boolean): Loading statedebounce(number): Debounce delay in msminChars(number): Min chars before searchmaxSuggestions(number): Max suggestions shownautofocus(boolean): Focus on mountclearable(boolean): Show clear buttondisabled(boolean): Disable inputsearchOnFocus(boolean): Search on focushighlightMatches(boolean): Highlight query matches
Events
update:modelValue: Query value updatesearch: Search triggeredselect: Item selectedfocus: Input focusedblur: Input blurredclear: Search clearedkeydown: Keyboard event
Slots
prefix: Content before inputsuffix: Content after inputsuggestion: Custom suggestion templateno-results: No results messageloading: Loading state contentclear-icon: Clear button icon
Usage Examples
- Basic Search:
<template> <UISearch v-model="query" placeholder="Search..." :suggestions="suggestions" @search="handleSearch" /></template>
<script setup>const query = ref('')const suggestions = ref([])
const handleSearch = async (value: string) => { suggestions.value = await fetchSuggestions(value)}</script>- Advanced Search with Filters:
<template> <div class="search-container"> <UISearch v-model="searchState.query" :suggestions="filteredResults" :loading="searchState.loading" :debounce="500" class="w-full" > <template #prefix> <UISelect v-model="searchState.category" :options="categories" class="w-32" /> </template>
<template #suggestion="{ item }"> <SearchResult :item="item" :highlight-terms="searchState.query.split(' ')" /> </template>
<template #no-results> <div class="p-4 text-center text-gray-500"> No results found for "{{ searchState.query }}" in {{ searchState.category }} </div> </template> </UISearch>
<div class="mt-4"> <UICheckboxGroup v-model="searchState.filters" :options="filterOptions" class="flex gap-4" /> </div> </div></template>
<script setup>const searchState = reactive({ query: '', category: 'all', filters: [], loading: false})
const filteredResults = computed(() => applyFilters(results.value, searchState))</script>- Async Search with Debounce:
<template> <UISearch v-model="searchQuery" :suggestions="searchResults" :loading="isSearching" :debounce="300" :min-chars="2" placeholder="Search users..." @search="performSearch" > <template #suggestion="{ item }"> <div class="flex items-center p-2"> <img :src="item.avatar" class="w-8 h-8 rounded-full" /> <div class="ml-3"> <div class="font-medium"> {{ item.name }} </div> <div class="text-sm text-gray-500"> {{ item.email }} </div> </div> </div> </template>
<template #loading> <div class="p-4"> <UISkeleton :rows="3" /> </div> </template> </UISearch></template>
<script setup>const searchQuery = ref('')const searchResults = ref([])const isSearching = ref(false)
const performSearch = async (query: string) => { if (!query) { searchResults.value = [] return }
isSearching.value = true try { const results = await searchUsers(query) searchResults.value = results } finally { isSearching.value = false }}</script>Best Practices
-
User Experience:
- Immediate feedback
- Clear loading states
- Keyboard navigation
- Mobile optimization
-
Performance:
- Debounce searches
- Cache results
- Limit suggestions
- Optimize rendering
-
Accessibility:
- ARIA labels
- Keyboard support
- Focus management
- Screen reader support
-
Error Handling:
- Network errors
- No results state
- Input validation
- Clear feedback
Common Use Cases
- Global Search:
<template> <div class="relative"> <UISearch v-model="globalSearch" :suggestions="searchResults" :loading="isLoading" class="w-full" placeholder="Search anything..." @select="navigateToResult" > <template #suggestion="{ item }"> <GlobalSearchResult :item="item" :query="globalSearch" /> </template>
<template #prefix> <CommandIcon class="w-5 h-5 text-gray-400" /> </template> </UISearch>
<div class="absolute right-3 top-2 text-sm text-gray-400"> Press ⌘K to search </div> </div></template>- Filter List:
<template> <div class="space-y-4"> <UISearch v-model="filterQuery" :suggestions="[]" placeholder="Filter items..." :searchOnFocus="true" @search="updateFilters" > <template #suffix> <UIButton v-if="hasFilters" size="sm" @click="clearFilters" > Clear </UIButton> </template> </UISearch>
<div class="grid gap-4"> <ListItem v-for="item in filteredItems" :key="item.id" :item="item" /> </div> </div></template>- Searchable Select:
<template> <UISearch v-model="selectedUser" :suggestions="userSuggestions" :loading="loadingUsers" placeholder="Select user..." @search="searchUsers" @select="handleUserSelect" > <template #suggestion="{ item }"> <UserListItem :user="item" :selected="isSelected(item)" /> </template>
<template #selected="{ item }"> <UserChip :user="item" :removable="true" @remove="clearSelection" /> </template> </UISearch></template>