Skip to main content
The VueListSearch component provides a search input that automatically triggers data fetching with debouncing to prevent excessive API calls.

Props

debounceTime
number
default:"1000"
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>

Custom Styled Input

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

With Clear Button

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

With Search Button

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

Build docs developers (and LLMs) love