Skip to content

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.

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

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

template
  <Select :loading="isLoadingOptions" />

Disabled

The disabled prop can be passed to disable the select component.

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

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

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

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.

vue
<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" uses an approximate match instead of an exact match to search for items in a list.

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

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

vue
<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>
        &nbsp;
        <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.

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

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

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

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