Alpine Autocomplete Component With optional tailwind styles
<div
  class="container"
  x-data=`autocomplete({})`
  @click.outside="open=false">
  <div x-show="limit > 1" class="selections">
    <template x-for="item in selected">
      <div>
        <span x-text="item.value"></span>
        <button type="button" @click="remove(item)" class="remove">x</button>
      </div>
    </template>
    <div x-show="selected?.length > 1" class="remove-all">
      <button type="button" @click="removeAll">Remove all</button>
    </div>
  </div>
  <div class="input-container" @click="open=true">
    <input
      x-model="query"
      x-ref="queryinput"
      :placeholder="placeholder"
      class="field"
      @input.debounce.200ms="fetchOptions"
    />
    <input type="hidden" :name="name" x-model="formVal" />
    <button
      x-show="selected.length > 0 && limit === 1"
      type="button"
      class="remove"
      @click="limit===1 ? removeAll(): clear()"
      >x
    </button>
  </div>
  <div x-show="open" class="options">
    <template x-for="option in options">
      <button
        x-text="option.value"
        type="button"
        @click="select(option); $refs.queryinput.focus()"
        class="option">
      </button>
    </template>
  </div>
</div>
document.addEventListener("alpine:init", () => {
    Alpine.data(
      "autocomplete",
      function ({
        getResults = async query => {
          // Fetch the results here, format them as [{key: obj.keyField, value: obj.valueField}, ....] and return
          const fruits = [
            "apple",
            "banana",
            "orange",
            "mango",
            "pineapple",
            "papaya",
            "watermelon",
            "pomegranate"
          ]
          return fruits
            .filter(item => item.includes(query))
            .map(item => ({key: item, value: item}))
        },
        // Max number of selections that can be made. Set it to 1 to use it as single select
        limit = 5,
        placeholder = "Search for a fruit",
        // Set selected=[{key: "apple", value: "apple"}] to have apple selected by default
        selected = [],
        // Max number of results that can be shown in the dropdown
        maxResults = 5
      }) {
        return {
          limit,
          placeholder,
          selected,
          options: [],
          query: "",
          open: false,

          init() {
            if (this.limit === 1) this.query = this.selected?.[0]?.value
          },

          formVal() {
            return this.selected.map(obj => obj.key).join("$")
          },

          async fetchOptions() {
            if (this.open === false) this.open = true
            if (this.query.length < 1) this.options = []
            this.options = (await getResults(this.query)).slice(0, maxResults)
          },

          clear() {
            this.query = ""
          },

          select(item) {
            if (!this.selected.find(obj => obj.key === item.key))
              this.selected = [...this.selected.slice(0, this.limit - 1), item]
            this.open = false
            if (this.limit === 1) {
              this.query = this.selected?.[0]?.value
            } else {
              this.clear()
            }
            this.$dispatch("change")
          },

          remove(item) {
            this.selected = this.selected.filter(obj => obj.key !== item.key)
            this.$dispatch("change")
          },

          removeAll() {
            this.selected = []
            this.clear()
            this.$dispatch("change")
          }
        }
      }
    )
  })
.container {
    @apply relative w-full;
  }
  .selections {
    @apply flex flex-wrap gap-2 mb-1;
  }
  .selections > div {
    @apply flex items-center gap-1 border whitespace-nowrap rounded-full px-2 py-0.5 text-sm border-blue-500;
  }
  .selections .remove {
    @apply pl-2 pr-1 text-base border-l ml-1;
  }
  .selections .remove-all {
    @apply flex items-center gap-1 border whitespace-nowrap rounded-full px-2 py-0.5 text-sm border-blue-500 bg-red-500 text-white;
  }
  .input-container {
    @apply flex h-10 border border-yellow-800;
  }
  .input-container .field {
    @apply border-none outline-none pl-2 bg-yellow-100 w-full;
  }
  .input-container .remove {
    @apply bg-yellow-100 border-l px-3 text-xl;
  }
  .options {
    @apply absolute shadow top-[100%] z-40 w-full bg-white flex flex-col;
  }
  .options .option {
    @apply py-2 text-left px-4 hover:bg-yellow-400 focus:bg-yellow-400;
  }
*More components will be added soon