Skip to content

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 from
  • placeholder (string): Placeholder text
  • searchable (boolean): Enable search
  • multiple (boolean): Allow multiple selection
  • clearable (boolean): Show clear button
  • disabled (boolean): Disable select
  • loading (boolean): Show loading state
  • size (string): Component size
  • error (boolean): Show error state
  • required (boolean): Mark as required
  • groupBy (string): Group options by key
  • labelBy (string): Option label key
  • valueBy (string): Option value key
  • maxHeight (number): Max dropdown height

Events

  • update:modelValue: Value updated
  • change: Selection changed
  • search: Search query changed
  • focus: Select focused
  • blur: Select blurred
  • clear: Selection cleared
  • open: Dropdown opened
  • close: Dropdown closed

Slots

  • option: Custom option template
  • selected: Custom selected value
  • no-options: No options message
  • no-results: No search results
  • loading: Loading state
  • prefix: Content before input
  • suffix: Content after input
  • group-label: Group header

Usage Examples

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

  1. User Experience:

    • Clear placeholder text
    • Appropriate dropdown width
    • Keyboard navigation
    • Search functionality
  2. Performance:

    • Debounce search
    • Virtualize large lists
    • Lazy loading
    • Optimize rendering
  3. Accessibility:

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

    • Clear error states
    • Required field handling
    • Form integration
    • Validation messages

Common Use Cases

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