Improvements made:

  • added cursor up and down navigation using focus plugin, similar to HeadlessUI
  • arrow down opens dropdown and focuses on first child
  • arrow up opens dropdown and focuses on last child

Source: https://alpinejs.dev/component/dropdown

This component is subject to a product license.

  • Handle: @alpine-components-dropdown
  • Preview:
  • Filesystem Path: components/10-other-examples/alpine-components/01-dropdown/dropdown.hbs
<!-- Alpine Plugins -->
<script defer src="https://unpkg.com/@alpinejs/[email protected]/dist/cdn.min.js"></script>

<!-- Alpine Core -->
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>

<style>
    [x-cloak] {
        display: none !important;
    }
</style>
<!--<h1 x-data="{ message: 'I ❤️ Alpine' }" x-text="message"></h1>-->

<div class="h-screen flex justify-center items-center">

    <div class="flex justify-center">
        <div x-data="{
            open: false,
            toggle() {
                if (this.open) {
                    return this.close()
                }

                this.$refs.button.focus()

                this.open = true
            },
            close(focusAfter) {
                if (! this.open) return

                this.open = false

                focusAfter && focusAfter.focus()
            }
        }" x-on:keydown.escape.prevent.stop="close($refs.button)" x-on:focusin.window="! $refs.panel.contains($event.target) && close()" x-id="['dropdown-button']" class="relative">
            <!-- Button -->
            <button x-ref="button" x-on:click="toggle()" @keydown.down.prevent.stop="open = true, $focus.within($refs.panel).first()" @keydown.up.prevent.stop="open = true, $focus.within($refs.panel).last()" :aria-expanded="open" :aria-controls="$id('dropdown-button')" type="button" class="flex items-center gap-2 bg-white px-5 py-2.5 rounded-md shadow">
                Options

                <!-- Heroicon: chevron-down -->
                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
                    <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
                </svg>
            </button>

            <!-- Panel -->
            <div x-ref="panel" @keydown.down.prevent.stop="$focus.next()" @keydown.up.prevent.stop="$focus.prev()" x-show="open" x-transition.origin.top.left x-on:click.outside="close($refs.button)" :id="$id('dropdown-button')" style="display: none;" class="absolute left-0 mt-2 w-40 rounded-md bg-white shadow-md">
                <a href="#" class="flex items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-50 disabled:text-gray-500">
                    New Task
                </a>

                <a href="#" class="flex items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-50 disabled:text-gray-500">
                    Edit Task
                </a>

                <a href="#" class="flex items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-50 disabled:text-gray-500">
                    <span class="text-red-600">Delete Task</span>
                </a>
            </div>
        </div>
    </div>

</div>

<!-- This is the code I wrote using the video walk-through, pretty similar to above -->
<!--<div class="flex flex-col gap-4 justify-center items-center h-screen">-->

<!--  <div x-data="{-->
<!--      open: false,-->
<!--      toggle() {-->
<!--        this.open = this.open ? this.close() : true ;-->
<!--      },-->
<!--      close(focusAfter) {-->
<!--        this.open = false;-->
<!--        focusAfter && focusAfter.focus();-->
<!--      }-->
<!--  }"-->
<!--       @keydown.escape.prevent.stop="close($refs.button)"-->
<!--       @focusin.window="! $refs.panel.contains($event.target) && close()"-->
<!--       x-id="['dropdown-button']"-->
<!--       class="relative"-->
<!--  >-->

<!--    &lt;!&ndash;  Button &ndash;&gt;-->
<!--    <button x-ref="button"-->
<!--            @click="toggle()"-->
<!--            :aria-expanded="open"-->
<!--            :aria-controls="$id('dropdown-button')"-->
<!--            type="button"-->
<!--            class="border border-black px-4 py-2 focus:outline-none focus:ring-4 focus:ring-aqua-400"-->
<!--    >-->
<!--      <span>Actions</span>-->
<!--      <span aria-hidden="true">&darr;</span>-->
<!--    </button>-->

<!--    &lt;!&ndash;  Panel &ndash;&gt;-->
<!--    <div x-ref="panel"-->
<!--         x-show="open"-->
<!--         x-transition.origin.top.left-->
<!--         @click.outside="close($refs.button)"-->
<!--         :id="$id('dropdown-button')"-->
<!--         x-cloak-->
<!--         class="absolute left-0 mt-2 bg-white border border-black"-->
<!--    >-->
<!--      <div>-->
<!--        <a href="#"-->
<!--           class="flex items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-50 disabled:text-gray-500">-->
<!--          New Task-->
<!--        </a>-->

<!--        <a href="#"-->
<!--           class="flex items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-50 disabled:text-gray-500">-->
<!--          Edit Task-->
<!--        </a>-->

<!--        <a href="#"-->
<!--           class="flex items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-50 disabled:text-gray-500">-->
<!--          <span class="text-red-600 whitespace-nowrap">Delete Task</span>-->
<!--        </a>-->
<!--      </div>-->
<!--    </div>-->

<!--  </div>-->

<!--  <div class="flex gap-x-2">-->

<!--    &lt;!&ndash;  Button &ndash;&gt;-->
<!--    <button-->
<!--            type="button"-->
<!--            class="border border-black px-4 py-2 focus:outline-none focus:ring-4 focus:ring-aqua-400"-->
<!--    >-->
<!--      <span>Other Button</span>-->
<!--    </button>-->

<!--    &lt;!&ndash;  Button &ndash;&gt;-->
<!--    <button-->
<!--            type="button"-->
<!--            class="border border-black px-4 py-2 focus:outline-none focus:ring-4 focus:ring-aqua-400"-->
<!--    >-->
<!--      <span>Another Button</span>-->
<!--    </button>-->

<!--  </div>-->

<!--</div>-->
<!-- Alpine Plugins -->
<script defer src="https://unpkg.com/@alpinejs/[email protected]/dist/cdn.min.js"></script>

<!-- Alpine Core -->
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>

<style>
  [x-cloak] {
    display: none !important;
  }
</style>
<!--<h1 x-data="{ message: 'I ❤️ Alpine' }" x-text="message"></h1>-->

<div class="h-screen flex justify-center items-center">

  <div class="flex justify-center">
    <div
            x-data="{
            open: false,
            toggle() {
                if (this.open) {
                    return this.close()
                }

                this.$refs.button.focus()

                this.open = true
            },
            close(focusAfter) {
                if (! this.open) return

                this.open = false

                focusAfter && focusAfter.focus()
            }
        }"
            x-on:keydown.escape.prevent.stop="close($refs.button)"
            x-on:focusin.window="! $refs.panel.contains($event.target) && close()"
            x-id="['dropdown-button']"
            class="relative"
    >
      <!-- Button -->
      <button
              x-ref="button"
              x-on:click="toggle()"
              @keydown.down.prevent.stop="open = true, $focus.within($refs.panel).first()"
              @keydown.up.prevent.stop="open = true, $focus.within($refs.panel).last()"
              :aria-expanded="open"
              :aria-controls="$id('dropdown-button')"
              type="button"
              class="flex items-center gap-2 bg-white px-5 py-2.5 rounded-md shadow"
      >
        Options

        <!-- Heroicon: chevron-down -->
        <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
          <path fill-rule="evenodd"
                d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
                clip-rule="evenodd"/>
        </svg>
      </button>

      <!-- Panel -->
      <div
              x-ref="panel"
              @keydown.down.prevent.stop="$focus.next()"
              @keydown.up.prevent.stop="$focus.prev()"
              x-show="open"
              x-transition.origin.top.left
              x-on:click.outside="close($refs.button)"
              :id="$id('dropdown-button')"
              style="display: none;"
              class="absolute left-0 mt-2 w-40 rounded-md bg-white shadow-md"
      >
        <a href="#"
           class="flex items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-50 disabled:text-gray-500">
          New Task
        </a>

        <a href="#"
           class="flex items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-50 disabled:text-gray-500">
          Edit Task
        </a>

        <a href="#"
           class="flex items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-50 disabled:text-gray-500">
          <span class="text-red-600">Delete Task</span>
        </a>
      </div>
    </div>
  </div>

</div>

<!-- This is the code I wrote using the video walk-through, pretty similar to above -->
<!--<div class="flex flex-col gap-4 justify-center items-center h-screen">-->

<!--  <div x-data="{-->
<!--      open: false,-->
<!--      toggle() {-->
<!--        this.open = this.open ? this.close() : true ;-->
<!--      },-->
<!--      close(focusAfter) {-->
<!--        this.open = false;-->
<!--        focusAfter && focusAfter.focus();-->
<!--      }-->
<!--  }"-->
<!--       @keydown.escape.prevent.stop="close($refs.button)"-->
<!--       @focusin.window="! $refs.panel.contains($event.target) && close()"-->
<!--       x-id="['dropdown-button']"-->
<!--       class="relative"-->
<!--  >-->

<!--    &lt;!&ndash;  Button &ndash;&gt;-->
<!--    <button x-ref="button"-->
<!--            @click="toggle()"-->
<!--            :aria-expanded="open"-->
<!--            :aria-controls="$id('dropdown-button')"-->
<!--            type="button"-->
<!--            class="border border-black px-4 py-2 focus:outline-none focus:ring-4 focus:ring-aqua-400"-->
<!--    >-->
<!--      <span>Actions</span>-->
<!--      <span aria-hidden="true">&darr;</span>-->
<!--    </button>-->

<!--    &lt;!&ndash;  Panel &ndash;&gt;-->
<!--    <div x-ref="panel"-->
<!--         x-show="open"-->
<!--         x-transition.origin.top.left-->
<!--         @click.outside="close($refs.button)"-->
<!--         :id="$id('dropdown-button')"-->
<!--         x-cloak-->
<!--         class="absolute left-0 mt-2 bg-white border border-black"-->
<!--    >-->
<!--      <div>-->
<!--        <a href="#"-->
<!--           class="flex items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-50 disabled:text-gray-500">-->
<!--          New Task-->
<!--        </a>-->

<!--        <a href="#"-->
<!--           class="flex items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-50 disabled:text-gray-500">-->
<!--          Edit Task-->
<!--        </a>-->

<!--        <a href="#"-->
<!--           class="flex items-center gap-2 w-full first-of-type:rounded-t-md last-of-type:rounded-b-md px-4 py-2.5 text-left text-sm hover:bg-gray-50 disabled:text-gray-500">-->
<!--          <span class="text-red-600 whitespace-nowrap">Delete Task</span>-->
<!--        </a>-->
<!--      </div>-->
<!--    </div>-->

<!--  </div>-->


<!--  <div class="flex gap-x-2">-->

<!--    &lt;!&ndash;  Button &ndash;&gt;-->
<!--    <button-->
<!--            type="button"-->
<!--            class="border border-black px-4 py-2 focus:outline-none focus:ring-4 focus:ring-aqua-400"-->
<!--    >-->
<!--      <span>Other Button</span>-->
<!--    </button>-->

<!--    &lt;!&ndash;  Button &ndash;&gt;-->
<!--    <button-->
<!--            type="button"-->
<!--            class="border border-black px-4 py-2 focus:outline-none focus:ring-4 focus:ring-aqua-400"-->
<!--    >-->
<!--      <span>Another Button</span>-->
<!--    </button>-->


<!--  </div>-->


<!--</div>-->
/* No context defined. */