Skip to content

Creating A Custom Vue Select Box Component

Published: at 08:10 PM

Finding The Perfect Component

Since starting my latest project I wanted a select box with a bit more style than the default HTML select. TailwindUI had exactly what I was looking for with their Select Menu with avatar.

Vue SelectBox Component

You can checkout the source on Github and a live demo.

Making An Easily Reusable Component

The select I found was exactly what I wanted but it needed to be easily reusable across many forms. The following was just to much to copy and paste on many different forms and would be difficult to maintain.

<template>
    <Listbox v-model="selected" as="div" :value="modelValue" @update:modelValue="value => emit('update:modelValue', value)">
      <div class="relative mt-1">
        <ListboxButton class="relative w-full cursor-default rounded-md bg-white pt-2 pb-2.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:text-sm sm:leading-6">
          <span class="flex items-center">
            <img v-if="selected && selected.avatar" :src="selected.avatar" alt="" class="h-5 w-5 flex-shrink-0 rounded-full" />
            <span class="ml-3 block truncate">{{ selectedName }}</span>
          </span>
          <span class="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2">
            <ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
          </span>
        </ListboxButton>
  
        <transition leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
          <ListboxOptions class="absolute z-10 mt-1 max-h-56 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
            <ListboxOption as="template" v-for="option in options" :key="option.value" :value="option" v-slot="{ active, selected }">
              <li :class="[active ? 'bg-indigo-600 text-white' : 'text-gray-900', 'relative cursor-default select-none py-2 pl-3 pr-9']">
                <div class="flex items-center">
                  <img v-if="option.avatar" :src="option.avatar" alt="" class="h-5 w-5 flex-shrink-0 rounded-full" />
                  <span :class="[selected ? 'font-semibold' : 'font-normal', 'ml-3 block truncate']">{{ option.label }}</span>
                </div>
  
                <span v-if="selected" :class="[active ? 'text-white' : 'text-indigo-600', 'absolute inset-y-0 right-0 flex items-center pr-4']">
                  <CheckIcon class="h-5 w-5" aria-hidden="true" />
                </span>
              </li>
            </ListboxOption>
          </ListboxOptions>
        </transition>
      </div>
    </Listbox>
  </template>

The Final Result

The final result is a simple component that takes 3 props.

v-model to bind the selected value to a variable.

:options to pass the array of objects that represent the select options.

placeholder a text value that is displayed when a value hasn’t been selected yet.

<SelectBox 
    v-model="form.country" 
    :options="countryOptions" 
    placeholder="Select Country"
/>