Tag Input Field

On projects, there is often a need to implement a tag input field. Let's explore how to address this requirement using the vs-vue3-select component. Consider the following task conditions:

  • As the user types, the field should display popular tags starting with the entered string, along with statistics on their usage.
  • The field should support the input of arbitrary tags.
  • Users should have the ability to paste a pre-prepared list of tags from the clipboard.
  • The height of the field should dynamically adjust to display all entered tags.
  • The field's value should be an array of strings containing the selected tags.

First, let's configure the component. Details about the parameters are described in the props section:

  • taggable enables the option for users to create custom options.
  • multiple enables the multiple selection mode.
  • filterable is disabled, as we will manage the filtering manually.
  • select-on-key-codes is set to [188,13] so that selection occurs when the user presses Enter or comma.
  • paste-separator is set to , to split the pasted string by this symbol.

Next, we pass the following functions:

  • create-option - a function that creates a new option from a string. Since options from the backend are objects with tags and usage statistics, we need to handle this conversion.
  • @search - a function to query the API to retrieve a list of options corresponding to the user's input. In this example, the function simulates an asynchronous server call.
  • reduce - based on the task conditions, the component should return an array of simple strings containing tags. We extract only the necessary information from the object describing the option.

We use the slot option to customize the display of each option to show tags and their usage statistics.

Additionally, we add the stretch CSS class to the component to ensure automatic height adjustment when adding tags.

<script setup>
import {computed, ref} from "vue";
import tags from '../data/tags';

const getTags = async function (search) {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve(tags.filter(tag => ~tag.label.indexOf(search))), 100)
  });
  options.value = await promise;
}

const onSearch = search => {
  if (search.length) {
    getTags(search);
  }
}

const displayUses = value => value < 1000 ? value : (Math.ceil(value / 100) / 10) + 'K';
const options = ref([]);
const value = ref([]);

const demoTags = computed(() => {
  return value.value.join(', ')
})

const createTag = value => {
  return {label: value, uses: 0}
}
</script>

<template>
  <div class="custom-result">Assigned Tags: {{ demoTags }}</div>
  <v-select
      v-model="value"
      :options="tags"

      taggable
      multiple
      :filtrable="false"
      :select-on-key-codes="[188, 13]"
      paste-separator=","

      :create-option="createTag"
      @search="onSearch"
      :reduce="option => option.label"

      class="stretch"
  >
    <template #option="{label, uses}">
      <span class="custom-option__label">{{ label }}</span>
      <span class="custom-option__uses">{{ displayUses(uses) }} video</span>
    </template>
  </v-select>
</template>
<style>
.stretch .vs__selected-options {
  flex-wrap: wrap;
}

.custom-result {
  margin-bottom: 24px;
  opacity: 0.5;
}

.custom-option__label {
  display: block;
}

.custom-option__uses {
  display: block;
  opacity: 0.6;
  font-size: 0.8em;
}
</style>

Let's see the resulting component in action. For convenience, the formatted value is displayed below the input field. Country names are used as examples of existing tags.

Assigned Tags:

Also, try copying a string to the clipboard and pasting it into the input field.