Published on

6 Tips To Create A AlpineJS Multiple Select Dropdown With Tags, Filter, Focus ↑↓ With Tailwind CSS

AlpineJS Multiple Select Dropdown with Tags, Filter, Focus ↑↓

Are you looking for a way to create a user-friendly and interactive dropdown menu for your website? Look no further than the AlpineJS Multiple Select Dropdown with Tags, Filter, Focus ↑↓ ui component. This component allows users to select multiple options from a dropdown menu, with the added bonus of tags, filtering, and focus control. And with the help of Tailwind CSS, the process of creating this component is made even easier. Here are 6 tips to help you create an AlpineJS Multiple Select Dropdown with Tags, Filter, Focus ↑↓ using Tailwind CSS.

What is Tailwind CSS?

Tailwind CSS is a utility-first CSS framework that makes it easy to design responsive and customizable user interfaces. It allows you to create complex layouts and styles by using pre-defined classes, rather than writing custom CSS. This saves time and effort, and allows you to focus on the functionality of your website or application.

The description of AlpineJS Multiple Select Dropdown with Tags, Filter, Focus ↑↓ ui component

The AlpineJS Multiple Select Dropdown with Tags, Filter, Focus ↑↓ ui component is a user-friendly and interactive dropdown menu that allows users to select multiple options. It includes the following features:

  • Tags: selected options are displayed as tags, making it easy for users to see their selections at a glance.
  • Filter: users can filter the options in the dropdown menu by typing in a search term.
  • Focus control: users can navigate the dropdown menu using the arrow keys, and select options using the enter key.

Why use Tailwind CSS to create a AlpineJS Multiple Select Dropdown with Tags, Filter, Focus ↑↓ ui component?

Tailwind CSS makes it easy to create a responsive and customizable user interface. By using pre-defined classes, you can quickly and easily style your components without having to write custom CSS. This saves time and effort, and allows you to focus on the functionality of your website or application. Additionally, Tailwind CSS integrates seamlessly with AlpineJS, making it the perfect choice for creating an AlpineJS Multiple Select Dropdown with Tags, Filter, Focus ↑↓ ui component.

The preview of AlpineJS Multiple Select Dropdown with Tags, Filter, Focus ↑↓ ui component

Free download of the AlpineJS Multiple Select Dropdown with Tags, Filter, Focus ↑↓'s source code

The source code of AlpineJS Multiple Select Dropdown with Tags, Filter, Focus ↑↓ ui component

<script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.7.1/cdn.js" defer></script>

<select id="select2" name="select2[]" class="hidden" multiple>
  <option value="above">Above</option>
  <option value="after">After</option>
  <option value="back" selected>Back</option>
  <option value="behind" selected>Behind</option>
  <option value="before" selected>Before</option>
  <option value="beyond" selected>Beyond</option>
  <option value="forward">Forward</option>
  <option value="front">Front</option>
  <option value="later">Later</option>
  <option value="under">Under</option>
</select>

<div class="relative flex" x-data="{ ...selectMultiple('select2') }">
  <!-- Selected -->
  <div
    class="flex flex-wrap rounded-3xl border border-teal-400"
    @click="isOpen = true;"
    @keydown.arrow-down.prevent="if(dropdown.length > 0) document.getElementById(elSelect.id+'_'+dropdown[0].index).focus();"
  >
    <template x-for="(option,index) in selected;" :key="option.value">
      <p
        class="m-1 cursor-pointer self-center whitespace-nowrap rounded-3xl bg-teal-200 p-2 text-xs hover:bg-red-300"
        x-text="option.text"
        @click="toggle(option)"
      ></p>
    </template>

    <input
      type="text"
      placeholder="Filter options"
      class="h-10 rounded-3xl pl-2"
      x-model="term"
      x-ref="input"
    />
  </div>

  <!-- Dropdown -->
  <div
    class="absolute z-10 mt-12 max-h-72 w-full overflow-y-auto rounded-xl bg-teal-100 "
    x-show="isOpen"
    @mousedown.away="isOpen = false"
  >
    <template x-for="(option,index) in dropdown" :key="option.value">
      <div
        class="cursor-pointer hover:bg-teal-200 focus:bg-teal-300 focus:outline-none"
        :class="(term.length > 0 && !option.text.toLowerCase().includes(term.toLowerCase())) && 'hidden';"
        x-init="$el.id = elSelect.id + '_' + option.index; $el.tabIndex = option.index;"
        @click="toggle(option)"
        @keydown.enter.prevent="toggle(option);"
        @keydown.arrow-up.prevent="if ($el.previousElementSibling != null) $el.previousElementSibling.focus();"
        @keydown.arrow-down.prevent="if ($el.nextElementSibling != null) $el.nextElementSibling.focus();"
      >
        <p class="p-2" x-text="option.text"></p>
      </div>
    </template>
  </div>
</div>

<script>
  document.addEventListener('alpine:init', () => {
    Alpine.data('selectMultiple', (elSelectId) => ({
      elSelect: document.getElementById(elSelectId),
      isOpen: false,
      term: '',

      selected: [],
      dropdown: [],

      // in the <select> element, set the attributes :
      //  "multiple" to avoid to Always set "selected" to the first item
      //  class="hidden" (better than hide it with javascript which has a slow reaction)
      init() {
        for (var index = 0; index < this.elSelect.options.length; index++) {
          if (this.elSelect.options[index].selected)
            this.selected.push(this.elSelect.options[index])
          else this.dropdown.push(this.elSelect.options[index])
        }
      },

      toggle(option) {
        var property1 = option.selected == true ? 'dropdown' : 'selected'
        var property2 = option.selected == true ? 'selected' : 'dropdown'

        option.selected = !option.selected

        // add
        this[property1].push(option)

        // remove
        this[property2] = this[property2].filter((opt, index) => {
          return opt.value != option.value
        })

        // reorder this.dropdown to their original select.options indexes
        if (property1 == 'dropdown')
          this.dropdown.sort((opt1, opt2) => (opt1.index > opt2.index ? 1 : -1))

        // after adding, re-focus the input
        if (option.selected) this.$refs.input.focus()
      },
    }))
  })
</script>

How to create a AlpineJS Multiple Select Dropdown with Tags, Filter, Focus ↑↓ with Tailwind CSS?

  1. Install Tailwind CSS: The first step is to install Tailwind CSS. You can do this by following the instructions on the Tailwind CSS website.

  2. Install AlpineJS: Next, you need to install AlpineJS. You can do this by following the instructions on the AlpineJS website.

  3. Create the HTML: The next step is to create the HTML for the dropdown menu. This will include a select element with multiple options, as well as a div element to display the selected options as tags.

<div x-data="{selected: []}">
  <select x-model="selected" multiple class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
    <option value="option1">Option 1</option>
    <option value="option2">Option 2</option>
    <option value="option3">Option 3</option>
    <option value="option4">Option 4</option>
    <option value="option5">Option 5</option>
  </select>
  <div class="flex flex-wrap mt-2">
    <template x-for="(item, index) in selected" :key="index">
      <div class="bg-gray-100 rounded-md px-2 py-1 mr-2 mb-2 flex items-center">
        <span x-text="item"></span>
        <button type="button" class="ml-2 text-gray-500 hover:text-gray-700" @click="selected.splice(index, 1)">
          <svg class="h-4 w-4 fill-current" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
            <path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/>
            <path fill-rule="evenodd" clip-rule="evenodd" d="M3 5a2 2 0 012-2h10a2 2 0 012 2v2h2a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2V9a2 2 0 012-2h2V5zm2 2v6h10V7H5z"/>
          </svg>
        </button>
      </div>
    </template>
  </div>
</div>
  1. Add AlpineJS functionality: The next step is to add AlpineJS functionality to the dropdown menu. This will include the ability to filter options, navigate the dropdown menu using arrow keys, and select options using the enter key.
<div x-data="{selected: [], options: [], search: '', focusIndex: null}" x-init="options = Array.from($el.querySelectorAll('option')).map(option => { return { value: option.value, text: option.innerText } })">
  <div class="relative">
    <input type="text" x-model="search" @keydown.arrow-up.prevent="focusIndex = (focusIndex === null) ? options.length - 1 : (focusIndex === 0 ? options.length - 1 : focusIndex - 1)" @keydown.arrow-down.prevent="focusIndex = (focusIndex === null) ? 0 : (focusIndex === options.length - 1 ? 0 : focusIndex + 1)" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 py-2 pl-3 pr-10 sm:text-sm" placeholder="Search...">
    <div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
      <svg class="h-4 w-4 fill-current text-gray-400" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
        <path fill-rule="evenodd" clip-rule="evenodd" d="M9 2a7 7 0 015.775 11.2l3.932 3.932a1 1 0 01-1.414 1.414l-3.932-3.932A7 7 0 119 2zm0 2a5 5 0 100 10 5 5 0 000-10z"/>
      </svg>
    </div>
  </div>
  <select x-model="selected" multiple class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" @keydown.enter.prevent="focusIndex !== null ? selected.push(options[focusIndex].value) : null" @keydown.space.prevent="focusIndex !== null ? selected.push(options[focusIndex].value) : null" @keydown.escape="focusIndex = null">
    <template x-for="(option, index) in options.filter(option => option.text.toLowerCase().includes(search.toLowerCase()))" :key="index">
      <option x-bind:value="option.value" x-text="option.text" @mouseover="focusIndex = index" @mouseout="focusIndex = null"></option>
    </template>
  </select>
  <div class="flex flex-wrap mt-2">
    <template x-for="(item, index) in selected" :key="index">
      <div class="bg-gray-100 rounded-md px-2 py-1 mr-2 mb-2 flex items-center">
        <span x-text="item"></span>
        <button type="button" class="ml-2 text-gray-500 hover:text-gray-700" @click="selected.splice(index, 1)">
          <svg class="h-4 w-4 fill-current" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
            <path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/>
            <path fill-rule="evenodd" clip-rule="evenodd" d="M3 5a2 2 0 012-2h10a2 2 0 012 2v2h2a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2V9a2 2 0 012-2h2V5zm2 2v6h10V7H5z"/>
          </svg>
        </button>
      </div>
    </template>
  </div>
</div>
  1. Style the component using Tailwind CSS: The final step is to style the component using Tailwind CSS. This will include adding classes to the HTML elements to define their appearance and behavior.
<div class="relative">
  <input type="text" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 py-2 pl-3 pr-10 sm:text-sm" placeholder="Search...">
  <div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
    <svg class="h-4 w-4 fill-current text-gray-400" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
      <path fill-rule="evenodd" clip-rule="evenodd" d="M9 2a7 7 0 015.775 11.2l3.932 3.932a1 1 0 01-1.414 1.414l-3.932-3.932A7 7 0 119 2zm0 2a5 5 0 100 10 5 5 0 000-10z"/>
    </svg>
  </div>
</div>
<select class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
  <template x-for="(option, index) in options.filter(option => option.text.toLowerCase().includes(search.toLowerCase()))" :key="index">
    <option x-bind:value="option.value" x-text="option.text" @mouseover="focusIndex = index" @mouseout="focusIndex = null"></option>
  </template>
</select>
<div class="flex flex-wrap mt-2">
  <template x-for="(item, index) in selected" :key="index">
    <div class="bg-gray-100 rounded-md px-2 py-1 mr-2 mb-2 flex items-center">
      <span x-text="item"></span>
      <button type="button" class="ml-2 text-gray-500 hover:text-gray-700" @click="selected.splice(index, 1)">
        <svg class="h-4 w-4 fill-current" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
          <path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/>
          <path fill-rule="evenodd" clip-rule="evenodd" d="M3 5a2 2 0 012-2h10a2 2 0 012 2v2h2a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2V9a2 2 0 012-2h2V5zm2 2v6h10V7H5z"/>
        </svg>
      </button>
    </div>
  </template>
</div>

Conclusion

Creating an AlpineJS Multiple Select Dropdown with Tags, Filter, Focus ↑↓ using Tailwind CSS is a simple and effective way to add an interactive and user-friendly dropdown menu to your website or application. By following these 6 tips, you can create a component that is both functional and visually appealing. With the help of Tailwind CSS and AlpineJS, the process is made even easier.