Usage
The InputMenu
component renders by default an Input component and is based on the ui.input
preset. You can use most of the Input
props to configure the display such as color, variant, size, placeholder, icon, disabled, etc.
You can use the ui
prop like the Input
component to override the default config. The uiMenu
prop can be used to override the default menu config.
Pass an array of strings or objects to the options
prop to display in the menu.
<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
const selected = ref(people[0])
</script>
<template>
<UInputMenu v-model="selected" :options="people" />
</template>
Objects
You can pass an array of objects to options
and either compare on the whole object or use the by
prop to compare on a specific key. You can configure which field will be used to display the label through the option-attribute
prop that defaults to label
.
<script setup lang="ts">
import type { Avatar } from '#ui/types'
const people = [{
id: 'benjamincanac',
label: 'benjamincanac',
href: 'https://github.com/benjamincanac',
target: '_blank',
avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' }
}, {
id: 'Atinux',
label: 'Atinux',
href: 'https://github.com/Atinux',
target: '_blank',
avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' }
}, {
id: 'smarroufin',
label: 'smarroufin',
href: 'https://github.com/smarroufin',
target: '_blank',
avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' }
}, {
id: 'nobody',
label: 'Nobody',
icon: 'i-heroicons-user-circle'
}]
const selected = ref(people[0])
</script>
<template>
<UInputMenu v-model="selected" :options="people">
<template #leading>
<UIcon v-if="selected.icon" :name="(selected.icon as string)" class="w-4 h-4 mx-0.5" />
<UAvatar v-else-if="selected.avatar" v-bind="(selected.avatar as Avatar)" size="3xs" class="mx-0.5" />
</template>
</UInputMenu>
</template>
Use the search-attributes
prop with an array of property names to search on each option object. Nested attributes can be accessed using dot.notation
. When the property value is an array or object, these are cast to string so these can be searched within.
<script setup lang="ts">
const options = [
{ id: 1, name: 'Wade Cooper', colors: ['red', 'yellow'] },
{ id: 2, name: 'Arlene Mccoy', colors: ['blue', 'yellow'] },
{ id: 3, name: 'Devon Webb', colors: ['green', 'blue'] },
{ id: 4, name: 'Tom Cook', colors: ['blue', 'red'] },
{ id: 5, name: 'Tanya Fox', colors: ['green', 'red'] },
{ id: 5, name: 'Hellen Schmidt', colors: ['green', 'yellow'] }
]
const selected = ref(options[1])
</script>
<template>
<UInputMenu
v-model="selected"
:options="options"
placeholder="Select a person"
by="id"
option-attribute="name"
:search-attributes="['name', 'colors']"
>
<template #option="{ option: person }">
<span v-for="color in person.colors" :key="color.id" class="h-2 w-2 rounded-full" :class="`bg-${color}-500 dark:bg-${color}-400`" />
<span class="truncate">{{ person.name }}</span>
</template>
</UInputMenu>
</template>
If you only want to select a single object property rather than the whole object as value, you can set the value-attribute
property. This prop defaults to null
.
<script setup lang="ts">
const people = [{
id: 1,
name: 'Wade Cooper'
}, {
id: 2,
name: 'Arlene Mccoy'
}, {
id: 3,
name: 'Devon Webb'
}, {
id: 4,
name: 'Tom Cook'
}]
const selected = ref(people[0].id)
</script>
<template>
<UInputMenu
v-model="selected"
:options="people"
value-attribute="id"
option-attribute="name"
/>
</template>
Icon
The InputMenu
has a button on the right to toggle the menu. Use the trailing-icon
prop to set a different icon or change it globally in ui.inputMenu.default.trailingIcon
. Defaults to i-heroicons-chevron-down-20-solid
.
<template>
<UInputMenu
trailing-icon="i-heroicons-chevron-up-down-20-solid"
class="w-full lg:w-48"
placeholder="Select a person"
:options="['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']"
/>
</template>
Use the selected-icon
prop to set a different icon or change it globally in ui.inputMenu.default.selectedIcon
. Defaults to i-heroicons-check-20-solid
.
<template>
<UInputMenu
selected-icon="i-heroicons-hand-thumb-up-solid"
class="w-full lg:w-48"
placeholder="Select a person"
:options="['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']"
/>
</template>
Searchable
Attributes
Use the search-attributes
prop with an array of property names to search on each option object. Nested attributes can be accessed using dot.notation
. When the property value is an array or object, these are cast to string so these can be searched within.
<script setup lang="ts">
const options = [
{ id: 1, name: 'Wade Cooper', colors: ['red', 'yellow'] },
{ id: 2, name: 'Arlene Mccoy', colors: ['blue', 'yellow'] },
{ id: 3, name: 'Devon Webb', colors: ['green', 'blue'] },
{ id: 4, name: 'Tom Cook', colors: ['blue', 'red'] },
{ id: 5, name: 'Tanya Fox', colors: ['green', 'red'] },
{ id: 5, name: 'Hellen Schmidt', colors: ['green', 'yellow'] }
]
const selected = ref(options[1])
</script>
<template>
<UInputMenu
v-model="selected"
:options="options"
placeholder="Select a person"
by="id"
option-attribute="name"
:search-attributes="['name', 'colors']"
>
<template #option="{ option: person }">
<span v-for="color in person.colors" :key="color.id" class="h-2 w-2 rounded-full" :class="`bg-${color}-500 dark:bg-${color}-400`" />
<span class="truncate">{{ person.name }}</span>
</template>
</UInputMenu>
</template>
Control the query
Use a v-model:query
to control the search query.
<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
const selected = ref()
const query = ref('Wade')
</script>
<template>
<UInputMenu
v-model="selected"
v-model:query="query"
:options="people"
placeholder="Select a person"
/>
</template>
Async search
Pass a function to the search
prop to customize the search behavior and filter options according to your needs. The function will receive the query as its first argument and should return an array.
Use the debounce
prop to adjust the delay of the function.
<script setup lang="ts">
const loading = ref(false)
const selected = ref()
async function search (q: string) {
loading.value = true
const users = await $fetch<any[]>('https://jsonplaceholder.typicode.com/users', { params: { q } })
loading.value = false
return users
}
</script>
<template>
<UInputMenu
v-model="selected"
:search="search"
:loading="loading"
placeholder="Search for a user..."
option-attribute="name"
trailing
by="id"
/>
</template>
Popper
Use the popper
prop to customize the popper instance.
Arrow
<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
const selected = ref(people[0])
</script>
<template>
<UInputMenu v-model="selected" :options="people" :popper="{ arrow: true }" />
</template>
Placement
<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
const selected = ref(people[0])
</script>
<template>
<UInputMenu v-model="selected" :options="people" :popper="{ placement: 'right-start' }" />
</template>
Offset
<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
const selected = ref(people[0])
</script>
<template>
<UInputMenu v-model="selected" :options="people" :popper="{ offsetDistance: 0 }" />
</template>
Slots
option
Use the #option
slot to customize the option content. You will have access to the option
, active
and selected
properties in the slot scope.
<script setup lang="ts">
const people = [
{ name: 'Wade Cooper', online: true },
{ name: 'Arlene Mccoy', online: false },
{ name: 'Devon Webb', online: false },
{ name: 'Tom Cook', online: true },
{ name: 'Tanya Fox', online: false },
{ name: 'Hellen Schmidt', online: true },
{ name: 'Caroline Schultz', online: true },
{ name: 'Mason Heaney', online: false },
{ name: 'Claudie Smitham', online: true },
{ name: 'Emil Schaefer', online: false }
]
const selected = ref(people[3])
</script>
<template>
<UInputMenu v-model="selected" :options="people" option-attribute="name">
<template #option="{ option: person }">
<span :class="[person.online ? 'bg-green-400' : 'bg-gray-200', 'inline-block h-2 w-2 flex-shrink-0 rounded-full']" aria-hidden="true" />
<span class="truncate">{{ person.name }}</span>
</template>
</UInputMenu>
</template>
option-empty
Use the #option-empty
slot to customize the content displayed when the searchable
prop is true
and there is no options. You will have access to the query
property in the slot scope.
<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
const selected = ref(people[0])
</script>
<template>
<UInputMenu v-model="selected" :options="people" searchable>
<template #option-empty="{ query }">
<q>{{ query }}</q> not found
</template>
</UInputMenu>
</template>
empty
Use the #empty
slot to customize the content displayed when there is no options. Defaults to No options.
.
<script setup lang="ts">
const people = []
const selected = ref()
</script>
<template>
<UInputMenu v-model="selected" :options="people">
<template #empty>
No people
</template>
</UInputMenu>
</template>
Props
undefined
null
"md"
"2xs"
"xs"
"sm"
"lg"
"xl"
null
{}
config.default.color
null
null
""
null
[]
config.default.variant
"outline"
"none"
null
config.default.loadingIcon
null
configMenu.default.trailingIcon
undefined
{}
null
configMenu.default.selectedIcon
"label"
null
null
200
{}
false
false
false
false
false
false
true
Config
ui
prop to override the input config and the uiMenu
prop to override the menu config.{
container: 'z-20 group',
trigger: 'inline-flex w-full',
width: 'w-full',
height: 'max-h-60',
base: 'relative focus:outline-none overflow-y-auto scroll-py-1',
background: 'bg-white dark:bg-gray-800',
shadow: 'shadow-lg',
rounded: 'rounded-md',
padding: 'p-1',
ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
empty: 'text-sm text-gray-400 dark:text-gray-500 px-2 py-1.5',
option: {
base: 'cursor-default select-none relative flex items-center justify-between gap-1',
rounded: 'rounded-md',
padding: 'px-1.5 py-1.5',
size: 'text-sm',
color: 'text-gray-900 dark:text-white',
container: 'flex items-center gap-1.5 min-w-0',
active: 'bg-gray-100 dark:bg-gray-900',
inactive: '',
selected: 'pe-7',
disabled: 'cursor-not-allowed opacity-50',
empty: 'text-sm text-gray-400 dark:text-gray-500 px-2 py-1.5',
icon: {
base: 'flex-shrink-0 h-5 w-5',
active: 'text-gray-900 dark:text-white',
inactive: 'text-gray-400 dark:text-gray-500',
},
selectedIcon: {
wrapper: 'absolute inset-y-0 end-0 flex items-center',
padding: 'pe-2',
base: 'h-5 w-5 text-gray-900 dark:text-white flex-shrink-0',
},
avatar: {
base: 'flex-shrink-0',
size: '2xs',
},
chip: {
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full',
},
},
transition: {
leaveActiveClass: 'transition ease-in duration-100',
leaveFromClass: 'opacity-100',
leaveToClass: 'opacity-0',
},
popper: {
placement: 'bottom-end',
},
default: {
selectedIcon: 'i-heroicons-check-20-solid',
trailingIcon: 'i-heroicons-chevron-down-20-solid',
},
arrow: {
base: 'invisible before:visible before:block before:rotate-45 before:z-[-1] before:w-2 before:h-2',
ring: 'before:ring-1 before:ring-gray-200 dark:before:ring-gray-700',
rounded: 'before:rounded-sm',
background: 'before:bg-white dark:before:bg-gray-700',
shadow: 'before:shadow',
placement: "group-data-[popper-placement*='right']:-left-1 group-data-[popper-placement*='left']:-right-1 group-data-[popper-placement*='top']:-bottom-1 group-data-[popper-placement*='bottom']:-top-1",
},
}