Skip to content

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 query
  • placeholder (string): Input placeholder text
  • suggestions (array): Search suggestions
  • loading (boolean): Loading state
  • debounce (number): Debounce delay in ms
  • minChars (number): Min chars before search
  • maxSuggestions (number): Max suggestions shown
  • autofocus (boolean): Focus on mount
  • clearable (boolean): Show clear button
  • disabled (boolean): Disable input
  • searchOnFocus (boolean): Search on focus
  • highlightMatches (boolean): Highlight query matches

Events

  • update:modelValue: Query value update
  • search: Search triggered
  • select: Item selected
  • focus: Input focused
  • blur: Input blurred
  • clear: Search cleared
  • keydown: Keyboard event

Slots

  • prefix: Content before input
  • suffix: Content after input
  • suggestion: Custom suggestion template
  • no-results: No results message
  • loading: Loading state content
  • clear-icon: Clear button icon

Usage Examples

  1. 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>
  1. 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>
  1. 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

  1. User Experience:

    • Immediate feedback
    • Clear loading states
    • Keyboard navigation
    • Mobile optimization
  2. Performance:

    • Debounce searches
    • Cache results
    • Limit suggestions
    • Optimize rendering
  3. Accessibility:

    • ARIA labels
    • Keyboard support
    • Focus management
    • Screen reader support
  4. Error Handling:

    • Network errors
    • No results state
    • Input validation
    • Clear feedback

Common Use Cases

  1. 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>
  1. 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>
  1. 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>