UISelect
UISelect
A versatile select component that provides a dropdown interface for selecting single or multiple options, with support for search, custom rendering, and grouping.
<template> <UISelect v-model="selectedValue" :options="options" :placeholder="'Select an option...'" :searchable="true" :clearable="true" :loading="isLoading" @change="handleChange" > <template #option="{ option }"> <div class="flex items-center space-x-3"> <img :src="option.avatar" class="w-6 h-6 rounded-full" /> <div> <div class="font-medium">{{ option.label }}</div> <div class="text-sm text-gray-500"> {{ option.description }} </div> </div> </div> </template> </UISelect></template>
<script setup lang="ts">interface Option { value: string label: string avatar?: string description?: string disabled?: boolean}
const options = ref<Option[]>([ { value: '1', label: 'Option 1', avatar: '/avatars/1.jpg', description: 'Description for option 1' }, { value: '2', label: 'Option 2', avatar: '/avatars/2.jpg', description: 'Description for option 2' }])
const selectedValue = ref('')const isLoading = ref(false)
const handleChange = (value: string) => { console.log('Selected:', value)}</script>Props
modelValue: Selected value(s)options(array): Options to select fromplaceholder(string): Placeholder textsearchable(boolean): Enable searchmultiple(boolean): Allow multiple selectionclearable(boolean): Show clear buttondisabled(boolean): Disable selectloading(boolean): Show loading statesize(string): Component sizeerror(boolean): Show error staterequired(boolean): Mark as requiredgroupBy(string): Group options by keylabelBy(string): Option label keyvalueBy(string): Option value keymaxHeight(number): Max dropdown height
Events
update:modelValue: Value updatedchange: Selection changedsearch: Search query changedfocus: Select focusedblur: Select blurredclear: Selection clearedopen: Dropdown openedclose: Dropdown closed
Slots
option: Custom option templateselected: Custom selected valueno-options: No options messageno-results: No search resultsloading: Loading stateprefix: Content before inputsuffix: Content after inputgroup-label: Group header
Usage Examples
- Basic Single Select:
<template> <UISelect v-model="country" :options="countries" placeholder="Select a country" /></template>
<script setup>const country = ref('')const countries = [ { value: 'us', label: 'United States' }, { value: 'uk', label: 'United Kingdom' }, { value: 'ca', label: 'Canada' }]</script>- Multiple Select with Search:
<template> <UISelect v-model="selectedUsers" :options="users" :multiple="true" :searchable="true" placeholder="Select users" class="w-full" > <template #option="{ option }"> <div class="flex items-center py-2"> <img :src="option.avatar" class="w-8 h-8 rounded-full" /> <div class="ml-3"> <div class="font-medium"> {{ option.name }} </div> <div class="text-sm text-gray-500"> {{ option.email }} </div> </div> </div> </template>
<template #selected="{ option }"> <div class="flex items-center space-x-2"> <img :src="option.avatar" class="w-5 h-5 rounded-full" /> <span>{{ option.name }}</span> </div> </template> </UISelect></template>
<script setup>const selectedUsers = ref([])const users = [ { id: 1, name: 'John Doe', email: 'john@example.com', avatar: '/avatars/john.jpg' }, // ... more users]</script>- Grouped Options:
<template> <UISelect v-model="selectedDepartment" :options="departments" :group-by="'category'" placeholder="Select department" > <template #group-label="{ group }"> <div class="flex items-center px-3 py-2"> <FolderIcon class="w-5 h-5 text-gray-400 mr-2" /> <span class="font-medium">{{ group }}</span> </div> </template>
<template #option="{ option }"> <div class="flex justify-between items-center"> <span>{{ option.label }}</span> <UIBadge v-if="option.count" size="sm" variant="gray" > {{ option.count }} </UIBadge> </div> </template> </UISelect></template>
<script setup>const selectedDepartment = ref('')const departments = [ { label: 'Engineering', value: 'eng', category: 'Technical', count: 15 }, { label: 'Design', value: 'design', category: 'Creative', count: 8 } // ... more departments]</script>Best Practices
-
User Experience:
- Clear placeholder text
- Appropriate dropdown width
- Keyboard navigation
- Search functionality
-
Performance:
- Debounce search
- Virtualize large lists
- Lazy loading
- Optimize rendering
-
Accessibility:
- ARIA labels
- Keyboard support
- Screen reader
- Focus management
-
Validation:
- Clear error states
- Required field handling
- Form integration
- Validation messages
Common Use Cases
- Form Field:
<template> <form @submit.prevent="handleSubmit"> <div class="space-y-4"> <label class="block"> <span class="text-sm font-medium"> Category </span>
<UISelect v-model="formData.category" :options="categories" :error="errors.category" required class="mt-1" />
<span v-if="errors.category" class="text-sm text-error-600" > {{ errors.category }} </span> </label>
<UIButton type="submit"> Save Changes </UIButton> </div> </form></template>- Async Options:
<template> <UISelect v-model="selectedCity" :options="cities" :loading="loading" :searchable="true" placeholder="Search for a city" @search="handleSearch" > <template #loading> <div class="p-4 text-center"> <UISpinner size="sm" /> <div class="mt-2 text-sm text-gray-500"> Loading cities... </div> </div> </template>
<template #no-results> <div class="p-4 text-center text-gray-500"> No cities found matching "{{ searchQuery }}" </div> </template> </UISelect></template>
<script setup>const selectedCity = ref('')const cities = ref([])const loading = ref(false)const searchQuery = ref('')
const handleSearch = async (query: string) => { searchQuery.value = query if (!query) return
loading.value = true try { const results = await searchCities(query) cities.value = results } finally { loading.value = false }}</script>- Cascading Selects:
<template> <div class="space-y-4"> <UISelect v-model="selectedCountry" :options="countries" placeholder="Select country" @change="handleCountryChange" />
<UISelect v-model="selectedState" :options="states" :disabled="!selectedCountry" :loading="loadingStates" placeholder="Select state" @change="handleStateChange" />
<UISelect v-model="selectedCity" :options="cities" :disabled="!selectedState" :loading="loadingCities" placeholder="Select city" /> </div></template>
<script setup>const selectedCountry = ref('')const selectedState = ref('')const selectedCity = ref('')
const loadingStates = ref(false)const loadingCities = ref(false)
const handleCountryChange = async () => { selectedState.value = '' selectedCity.value = ''
if (!selectedCountry.value) return
loadingStates.value = true try { states.value = await fetchStates(selectedCountry.value) } finally { loadingStates.value = false }}
const handleStateChange = async () => { selectedCity.value = ''
if (!selectedState.value) return
loadingCities.value = true try { cities.value = await fetchCities(selectedState.value) } finally { loadingCities.value = false }}</script>