Select
The Select
component is used to create a menu with selectable options. It is commonly used to allow users to choose a single or multiple values of a list of options.
Single selection
The Select
component will select a single option when the single
prop is passed to it.
<script setup lang="ts">
import { ref } from 'vue';
import Select from '../../../src/components/Select/Select.vue';
interface SelectOption {
name: string;
id: number;
}
const options = ref<SelectOption[]>([
{ name: 'First', id: 1 },
{ name: 'Second', id: 2 },
{ name: 'Third', id: 3 },
{ name: 'Fourth', id: 4 },
{ name: 'Fifth', id: 5 },
{ name: 'Johnathon Longnamestonson', id: 6 },
]);
const selected = ref<SelectOption>(options.value[1]);
</script>
<template>
<Select v-model="selected" class="tw-max-w-[260px]" label="My Select" single :options="options" />
<div class="tw-pt-4">Selected: {{ selected?.name }}</div>
</template>
Multiple selection
By default, the Select
component will receive multiple selected values and automatically treats any overflow of its items.
INFO
Notice the "overflow" treatment when many options are selected.
<script setup lang="ts">
import { computed, ref } from 'vue';
import Select from '../../../src/components/Select/Select.vue';
interface SelectOption {
name: string;
id: number;
}
const options = ref<SelectOption[]>([
{ name: 'First', id: 1 },
{ name: 'Second', id: 2 },
{ name: 'Third', id: 3 },
{ name: 'Fourth', id: 4 },
{ name: 'Fifth', id: 5 },
{ name: 'Johnathon Longnamestonson', id: 6 },
]);
const selected = ref<SelectOption[]>([]);
const displaySelected = computed(() => selected.value.map((s) => s.name).join(', '));
</script>
<template>
<Select v-model="selected" class="tw-max-w-[260px]" label="My Select" :options="options" />
<div class="tw-pt-4">Selected: {{ displaySelected }}</div>
</template>
Loading
The loading
prop can be passed to render a loading icon when fetching options from another source.
Search Loading
The loading
prop is only intended to be used when options are asynchronously loaded one time. To reload the options every time the user searches, see Search with an HTTP request.
<Select :loading="isLoadingOptions" />
Disabled
The disabled
prop can be passed to disable the select component.
<script setup lang="ts">
import { computed, ref } from 'vue';
import Select from '../../../src/components/Select/Select.vue';
interface SelectOption {
name: string;
id: number;
}
const options = ref<SelectOption[]>([
{ name: 'First', id: 1 },
{ name: 'Second', id: 2 },
{ name: 'Third', id: 3 },
{ name: 'Fourth', id: 4 },
{ name: 'Fifth', id: 5 },
{ name: 'Johnathon Longnamestonson', id: 6 },
]);
const selected = ref<SelectOption[]>([options.value[1]]);
const displaySelected = computed(() => selected.value.map((s) => s.name).join(', '));
</script>
<template>
<Select v-model="selected" class="tw-max-w-[260px]" label="My Select" disabled :options="options" />
<div class="tw-pt-4">Selected: {{ displaySelected }}</div>
</template>
Errors
The error
prop can be passed to display an error message related to the selected options.
<script setup lang="ts">
import { computed, ref } from 'vue';
import Select from '../../../src/components/Select/Select.vue';
interface SelectOption {
name: string;
id: number;
}
const options = ref<SelectOption[]>([
{ name: 'First', id: 1 },
{ name: 'Second', id: 2 },
{ name: 'Third', id: 3 },
{ name: 'Fourth', id: 4 },
{ name: 'Fifth', id: 5 },
{ name: 'Johnathon Longnamestonson', id: 6 },
]);
const selected = ref<SelectOption>();
const errorText = computed(() => (selected.value ? '' : 'Required'));
</script>
<template>
<Select v-model="selected" class="tw-max-w-[260px]" label="My Select" single :error="errorText" :options="options" />
<div class="tw-pt-4">Selected: {{ selected?.name }}</div>
</template>
Prevent empty
The prevent-empty
prop used to prevent the select input from having an empty value. When set to true
, the user is required to select an option from the dropdown. This ensures that the select input always has a valid value.
<script setup lang="ts">
import { ref } from 'vue';
import Select from '../../../src/components/Select/Select.vue';
interface SelectOption {
name: string;
id: number;
}
const options = ref<SelectOption[]>([
{ name: 'First', id: 1 },
{ name: 'Second', id: 2 },
{ name: 'Third', id: 3 },
{ name: 'Fourth', id: 4 },
{ name: 'Fifth', id: 5 },
{ name: 'Johnathon Longnamestonson', id: 6 },
]);
const selected = ref<SelectOption>(options.value[1]);
</script>
<template>
<Select v-model="selected" class="tw-max-w-[260px]" label="My Select" single prevent-empty :options="options" />
<div class="tw-pt-4">Selected: {{ selected?.name }}</div>
</template>
Filtering with Search
Specific properties
Searching is enabled by default. The display-by
prop controls which property is searchable for each option and its default value is "name"
.
To override the default searchable property or to search by multiple properties, provide an array of property names to the search-by
prop.
<script setup lang="ts">
import { computed, ref } from 'vue';
import Select from '../../../src/components/Select/Select.vue';
interface SelectOption {
name: string;
id: number;
}
const options = ref<SelectOption[]>([
{ name: 'First', id: 1 },
{ name: 'Second', id: 2 },
{ name: 'Third', id: 3 },
{ name: 'Fourth', id: 4 },
{ name: 'Fifth', id: 5 },
{ name: 'Johnathon Longnamestonson', id: 6 },
]);
const selected = ref<SelectOption[]>([]);
const displaySelected = computed(() => selected.value.map((s) => s.name).join(', '));
</script>
<template>
<Select
v-model="selected"
class="tw-max-w-[260px]"
label="My Select"
hint="Search by id or name"
:options="options"
:search-by="['id', 'name']"
/>
<div class="tw-pt-4">Selected: {{ displaySelected }}</div>
</template>
Fuzzy Search
"Fuzzy Search" uses an approximate match instead of an exact match to search for items in a list.
<script setup lang="ts">
import { computed, ref } from 'vue';
import Select from '../../../src/components/Select/Select.vue';
interface SelectOption {
name: string;
id: number;
}
const options = ref<SelectOption[]>([
{ name: 'First', id: 1 },
{ name: 'Second', id: 2 },
{ name: 'Third', id: 3 },
{ name: 'Fourth', id: 4 },
{ name: 'Fifth', id: 5 },
{ name: 'Johnathon Longnamestonson', id: 6 },
]);
const selected = ref<SelectOption[]>([]);
const displaySelected = computed(() => selected.value.map((s) => s.name).join(', '));
</script>
<template>
<Select
v-model="selected"
class="tw-max-w-[260px]"
label="My Select"
hint='Search for "jon"'
use-fuzzy-search
:options="options"
/>
<div class="tw-pt-4">Selected: {{ displaySelected }}</div>
</template>
With an HTTP request
The @search
event can be used to populate a Select's options using an HTTP request.
Debouncing
The search input uses debounce
internally, so there is no need to debounce your @search
event handler.
Loading Indicator:
To ensure the loading indicator within the search input is visible while your HTTP request is pending, your @search
event handler needs to return a Promise
that resolves after the HTTP request is complete. Click the "Show Code" button below the demo for an example.
disable-filtering
Always use the disable-filtering
prop when populating options
with an HTTP request in order to disable Select's internal searching/filtering since the HTTP request will do the searching/filtering.
<script setup lang="ts">
import { computed, ref } from 'vue';
import Select from '../../../src/components/Select/Select.vue';
interface SelectOption {
name: string;
id: string;
}
const options = ref<SelectOption[]>([]);
const selected = ref<SelectOption[]>([]);
const displaySelected = computed(() => selected.value.map((s) => s.name).join(', '));
function getProductsFromApi(searchTerm?: string): Promise<{ results: Array<{ id: string; name: string }> }> {
if (!searchTerm?.trim()) {
return Promise.resolve({ results: [] });
}
// A mocked backend response
return new Promise((resolve) => {
setTimeout(() => {
resolve({
results: Array.from({ length: Math.ceil(Math.random() * 5) }, (_, i) => ({
id: `${searchTerm}-${i + 1}`,
name: `${searchTerm} ${i + 1}`,
})),
});
}, 500);
});
}
const onSearch = async (searchTerm?: string) => {
options.value = [];
// The `await` ensures the loading indicator in the search input remains visible while the HTTP request is pending
const response = await getProductsFromApi(searchTerm);
options.value = response.results;
};
</script>
<template>
<Select
v-model="selected"
class="tw-max-w-[260px]"
label="My Select"
disable-filtering
preserve-search-term
:options="options"
@search="onSearch"
/>
<div class="tw-pt-4">Selected: {{ displaySelected }}</div>
</template>
Custom options
The option
slot can be leverage to customize the option text. This slot exposes the option data.
<script setup lang="ts">
import { computed, ref } from 'vue';
import Select from '../../../src/components/Select/Select.vue';
interface OptionWithQuantity {
name: string;
id: number;
quantity: number;
}
const options = ref<OptionWithQuantity[]>([
{ name: 'First', id: 1, quantity: 100 },
{ name: 'Second', id: 2, quantity: 200 },
{ name: 'Third', id: 3, quantity: 300 },
{ name: 'Fourth', id: 4, quantity: 400 },
{ name: 'Fifth', id: 5, quantity: 500 },
]);
const selected = ref<OptionWithQuantity[]>([]);
const displaySelected = computed(() => selected.value.map((s) => s.name).join(', '));
</script>
<template>
<Select v-model="selected" class="tw-max-w-[260px]" label="My Select" :options="options">
<template #option="{ option }">
<div class="tw-flex tw-items-center tw-justify-between">
<span class="tw-truncate">{{ option.name }}</span>
<small>({{ option.quantity }} Available)</small>
</div>
</template>
</Select>
<div class="tw-pt-4">Selected: {{ displaySelected }}</div>
</template>
Custom icons
The icon
prop can be passed to replace the default 'caret-down' icon.
<script setup lang="ts">
import { ref } from 'vue';
import Select from '../../../src/components/Select/Select.vue';
const options = ref([
{ name: 'First', id: 1 },
{ name: 'Second', id: 2 },
{ name: 'Third', id: 3 },
{ name: 'Fourth', id: 4 },
{ name: 'Fifth', id: 5 },
{ name: 'Johnathon Longnamestonson', id: 6 },
]);
</script>
<template>
<div class="tw-flex tw-max-w-[260px] tw-flex-col tw-gap-4">
<Select label="Edit" icon="edit" :options="options" />
<Select label="Add" icon="plus" :options="options" />
<Select label="View" icon="document-view" :options="options" />
</div>
</template>
Custom selected text
The select-item-type
prop can be passed to customize the selected option text. By default, It will render "3 selected"
when 3 items are selected and there is not enough space to display them all.
<script setup lang="ts">
import { computed, ref } from 'vue';
import Select from '../../../src/components/Select/Select.vue';
interface SelectOption {
name: string;
id: number;
}
const options = ref<SelectOption[]>([
{ name: 'First', id: 1 },
{ name: 'Second', id: 2 },
{ name: 'Third', id: 3 },
{ name: 'Fourth', id: 4 },
{ name: 'Fifth', id: 5 },
{ name: 'Johnathon Longnamestonson', id: 6 },
]);
const selected = ref<SelectOption[]>([]);
const displaySelected = computed(() => selected.value.map((s) => s.name).join(', '));
</script>
<template>
<Select v-model="selected" class="tw-max-w-[260px]" label="My Select" select-item-type="items" :options="options" />
<div class="tw-pt-4">Selected: {{ displaySelected }}</div>
</template>
Accessibility
The Select
component supports keyboard navigation, allowing users to use Tab, the arrow keys, Enter, and Space to interact with it. The Esc key can be used to dismiss the Select component.
<script setup lang="ts">
import { ref } from 'vue';
import Button from '../../../src/components/Button/Button.vue';
import Checkbox from '../../../src/components/Checkbox/Checkbox.vue';
import Input from '../../../src/components/Input/Input.vue';
import Select from '../../../src/components/Select/Select.vue';
interface Option {
name: string;
id: number;
}
const options = ref<Option[]>([
{ name: 'First', id: 1 },
{ name: 'Second', id: 2 },
{ name: 'Third', id: 3 },
{ name: 'Fourth', id: 4 },
{ name: 'Fifth', id: 5 },
]);
const selected = ref<Option[]>([]);
const enableTeleport = ref(false);
</script>
<template>
<div class="tw-flex tw-flex-wrap tw-gap-4">
<Checkbox v-model:checked="enableTeleport" class="tw-flex-1" label="Enable Teleport on Select" />
<Input class="tw-flex-1" label="Name" placeholder="LeafLink" />
<Input class="tw-flex-1" label="SSN" placeholder="123-45-6789" />
<Select v-model="selected" class="tw-flex-1" label="Options" :enable-teleport="enableTeleport" :options="options" />
<Button class="tw-min-w-auto tw-self-end">Save</Button>
</div>
</template>
Custom Update Behavior
It's possible to programmatically select an option by using the :model-value
prop in combination with the @update:model-value
event emitted by the the Select
component.
<script setup lang="ts">
import { ref } from 'vue';
import Button from '../../../src/components/Button/Button.vue';
import Modal from '../../../src/components/Modal/Modal.vue';
import Select from '../../../src/components/Select/Select.vue';
interface SelectOption {
name: string;
id: number;
}
const options = ref<SelectOption[]>([
{ name: 'First', id: 1 },
{ name: 'Second', id: 2 },
{ name: 'Third', id: 3 },
{ name: 'Fourth', id: 4 },
{ name: 'Fifth', id: 5 },
{ name: 'Johnathon Longnamestonson', id: 6 },
]);
const appliedSelection = ref<SelectOption | undefined>(options.value[1]);
const workingSelection = ref<SelectOption | undefined>(options.value[1]);
const isModalOpen = ref(false);
function onUpdateSelected(newValue?: SelectOption) {
workingSelection.value = newValue;
isModalOpen.value = true;
}
function onModalCancel() {
workingSelection.value = appliedSelection.value;
isModalOpen.value = false;
}
function onModalContinue() {
appliedSelection.value = workingSelection.value;
isModalOpen.value = false;
}
</script>
<template>
<Select
class="tw-max-w-[260px]"
label="My Select"
single
:options="options"
:model-value="workingSelection"
@update:model-value="onUpdateSelected"
/>
<div class="tw-pt-4">Applied Selection: {{ appliedSelection?.name || 'None' }}</div>
<Modal v-model:is-open="isModalOpen" @dismiss="isModalOpen = false">
Apply the following selection?
<br />
<br />
<pre>{{ workingSelection?.name || 'None' }}</pre>
<template #actions>
<Button secondary @click="onModalCancel">Cancel</Button>
<Button @click="onModalContinue">Continue</Button>
</template>
</Modal>
</template>
API
See the documentation below for a complete reference to all the props and classes available to the components mentioned here.