The VueListSearch component provides a search input that automatically triggers data fetching with debouncing to prevent excessive API calls.
Props
Delay in milliseconds before triggering the search after the user stops typing. This prevents making an API request on every keystroke.<!-- Wait 500ms after user stops typing -->
<VueListSearch :debounce-time="500" />
<!-- Wait 2 seconds (useful for expensive searches) -->
<VueListSearch :debounce-time="2000" />
<!-- No debounce (not recommended) -->
<VueListSearch :debounce-time="0" />
Behavior
- Automatically triggers search after the debounce period
- Resets pagination to page 1 when search changes
- Search value is synced with the parent VueList component
- Uses lodash-es debounce internally
Slots
Default Slot
Customize the search input completely:
<VueListSearch v-slot="{ search, setSearch }">
<div class="search-container">
<input
type="text"
:value="search"
@input="setSearch($event.target.value)"
placeholder="Search users..."
class="search-input"
/>
<Icon name="search" />
</div>
</VueListSearch>
Current search query value
Function to update the search query (already debounced)
Usage Examples
Basic Usage
<VueList endpoint="/api/users">
<VueListSearch />
<VueListItems>
<template #item="{ item }">
<div>{{ item.name }}</div>
</template>
</VueListItems>
</VueList>
<VueList endpoint="/api/users">
<VueListSearch v-slot="{ search, setSearch }">
<div class="relative">
<input
type="search"
:value="search"
@input="setSearch($event.target.value)"
placeholder="Search by name or email..."
class="w-full px-4 py-2 pl-10 border rounded-lg focus:outline-none focus:ring-2"
/>
<svg
class="absolute left-3 top-2.5 w-5 h-5 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
</div>
</VueListSearch>
<VueListItems>
<template #item="{ item }">
<div>{{ item.name }}</div>
</template>
</VueListItems>
</VueList>
<VueList endpoint="/api/products">
<VueListSearch v-slot="{ search, setSearch }">
<div class="flex gap-2">
<input
type="text"
:value="search"
@input="setSearch($event.target.value)"
placeholder="Search products..."
class="flex-1 px-4 py-2 border rounded"
/>
<button
v-if="search"
@click="setSearch('')"
class="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
>
Clear
</button>
</div>
</VueListSearch>
<VueListItems>
<template #item="{ item }">
<div>{{ item.name }}</div>
</template>
</VueListItems>
</VueList>
<script setup>
import { ref } from 'vue'
const searchInput = ref('')
function handleSearch(setSearch) {
setSearch(searchInput.value)
}
</script>
<template>
<VueList endpoint="/api/articles">
<VueListSearch :debounce-time="0" v-slot="{ setSearch }">
<div class="flex gap-2">
<input
v-model="searchInput"
type="text"
placeholder="Search articles..."
class="flex-1 px-4 py-2 border rounded"
@keyup.enter="handleSearch(setSearch)"
/>
<button
@click="handleSearch(setSearch)"
class="px-6 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Search
</button>
</div>
</VueListSearch>
<VueListItems>
<template #item="{ item }">
<div>{{ item.title }}</div>
</template>
</VueListItems>
</VueList>
</template>
With Loading Indicator
<VueList endpoint="/api/users" v-slot="{ isLoading }">
<VueListSearch v-slot="{ search, setSearch }">
<div class="relative">
<input
type="text"
:value="search"
@input="setSearch($event.target.value)"
placeholder="Search..."
class="w-full px-4 py-2 border rounded"
:disabled="isLoading"
/>
<div v-if="isLoading" class="absolute right-3 top-2.5">
<Spinner class="w-5 h-5" />
</div>
</div>
</VueListSearch>
<VueListItems>
<template #item="{ item }">
<div>{{ item.name }}</div>
</template>
</VueListItems>
</VueList>
Multiple Search Fields
<script setup>
import { ref, watch } from 'vue'
const nameSearch = ref('')
const emailSearch = ref('')
const filters = ref({})
watch([nameSearch, emailSearch], ([name, email]) => {
filters.value = {
name: name,
email: email
}
})
</script>
<template>
<VueList
endpoint="/api/users"
v-model:filters="filters"
>
<div class="grid grid-cols-2 gap-4">
<div>
<label>Search by Name</label>
<input
v-model="nameSearch"
type="text"
placeholder="Name..."
class="w-full px-4 py-2 border rounded"
/>
</div>
<div>
<label>Search by Email</label>
<input
v-model="emailSearch"
type="text"
placeholder="Email..."
class="w-full px-4 py-2 border rounded"
/>
</div>
</div>
<VueListItems>
<template #item="{ item }">
<div>{{ item.name }} - {{ item.email }}</div>
</template>
</VueListItems>
</VueList>
</template>
With Results Count
<VueList endpoint="/api/products" v-slot="{ count, isLoading }">
<div class="space-y-4">
<VueListSearch />
<div v-if="!isLoading" class="text-sm text-gray-600">
Found {{ count }} result{{ count !== 1 ? 's' : '' }}
</div>
<VueListItems>
<template #item="{ item }">
<div>{{ item.name }}</div>
</template>
</VueListItems>
</div>
</VueList>
Styling
The component renders with a default class name:
.vue-list__search {
/* Your styles */
}
Best Practices
Debounce Time Recommendations:
- Fast endpoints: 300-500ms
- Standard endpoints: 1000ms (default)
- Expensive searches: 1500-2000ms
The search automatically resets pagination to page 1, ensuring users see results from the beginning.
The VueListSearch component must be used inside a VueList component to access search state and methods.