Tailwind CSS Dropdown with details-menu Web Component

• 4 min read

If you didn't know yet, GitHub maintains a growing list of open sourced Web Components.

One is love to use is details-menu-element. It adds a <details-menu> element which can be used in combination with <details> and <summary> to create dropdowns. The best thing: You don't have to write any additional JavaScript to make it work. Everything is bundled inside <details-menu>.

I use this in websites where I don't want to include a heavy JavaScript framework like Vue.js or React, but still need an accessible dropdown.

In this example on CodePen, I've adapted the Tailwind UI dropdown example to use <detail-menu>.

I've also sprinkled in some Alpine.js to close the dropdown if the user clicks anywhere outside of it.

Source Code

The HTML without any Tailwind CSS classes or Alpine.js attributes looks like this.

<details open>
    <summary>
        <div>
            Options
            <!-- Heroicon name: solid/chevron-down -->
            <svg
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 20 20"
                fill="currentColor"
                aria-hidden="true"
            >
                <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>
        </div>
    </summary>
    <details-menu role="menu">
        <div role="none">
            <a href="#" role="menuitem">Account settings</a>
            <a href="#" role="menuitem">Support</a>
            <a href="#" role="menuitem">License</a>
            <form method="POST" action="#" role="none">
                <button type="submit" role="menuitem">Sign out</button>
            </form>
        </div>
    </details-menu>
</details>

Next I've added the Alpine.js attributes. We init Alpine.js on the <details>-element and give it a $ref. We then listen to the @click.away event and remove the open attribute, to close/hide the dropdown.

<details x-data x-ref="dropdown" @click.away="$refs.dropdown.removeAttribute('open');" open>
    <summary>
        <div>
            Options
            <!-- Heroicon name: solid/chevron-down -->
            <svg
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 20 20"
                fill="currentColor"
                aria-hidden="true"
            >
                <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>
        </div>
    </summary>
    <details-menu role="menu">
        <div role="none">
            <a href="#" role="menuitem">Account settings</a>
            <a href="#" role="menuitem">Support</a>
            <a href="#" role="menuitem">License</a>
            <form method="POST" action="#" role="none">
                <button type="submit" role="menuitem">Sign out</button>
            </form>
        </div>
    </details-menu>
</details>

And here is the final component with its Tailwind CSS classes added.

<!-- https://tailwindui.com/components/application-ui/elements/dropdowns#component-f8a14da22f26a67757b19f2fe3ca00ed -->

<details
    open
    x-data
    x-ref="dropdown"
    @click.away="$refs.dropdown.removeAttribute('open');"
    class="relative inline-block text-left"
>
    <summary>
        <div
            class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500"
        >
            Options
            <!-- Heroicon name: solid/chevron-down -->
            <svg
                class="-mr-1 ml-2 h-5 w-5"
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 20 20"
                fill="currentColor"
                aria-hidden="true"
            >
                <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>
        </div>
    </summary>

    <details-menu
        role="menu"
        class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
    >
        <div class="py-1" role="none">
            <a
                href="#"
                class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
                role="menuitem"
                >Account settings</a
            >
            <a
                href="#"
                class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
                role="menuitem"
                >Support</a
            >
            <a
                href="#"
                class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
                role="menuitem"
                >License</a
            >
            <form method="POST" action="#" role="none">
                <button
                    type="submit"
                    class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
                    role="menuitem"
                >
                    Sign out
                </button>
            </form>
        </div>
    </details-menu>
</details>

On the JavaScript side, you only have to import two libraries to make everything work.

  • import '@github/details-menu-element'
  • import 'alpinejs'

Outro

Web Components haven't catched on in mainstream web development – or at least not in my bubble – but I see much potential in them.

Especially components like <details-menu> which are "headless" and don't come with any styles attached to them, have a bright future a head of them.
The team at Tailwind Labs for example is already working on Headless UI. A set of UI components which are accessible and unstyled and can be used in React or Vue.

I have the felling that the growing popularity of utility CSS frameworks will spill over to Web Components and we see Web Components used more in mainstream web development projects.


As mentioned in the beginning, GitHub open sourced quite a few Web Components they use on github.com. Here's a quick list of components I started using in some of my projects.

Check them out. Maybe you can replace an existing component in your projects with them and reduce the size of your bundles.

Webmentions

    Likes and more

    Replies

  • Photo of Lennart Fischer Lennart Fischer replied

    Great article! Thanks for sharing it! 😊 I don’t know whether you already noticed it, but all GitHub component links lead to the clipboard-copy element. Maybe the clipboard-copy is also responsible for that? 😉

    March 21st, 2021

  • Photo of Loris Loris replied

    Nice! I didn’t know about them. What do they do behind the scenes exactly? Add some aria and sr-only things? Do they have other AMP-like advantages?

    March 21st, 2021

  • Photo of Stefan Zweifel 🌿 Stefan Zweifel 🌿 replied

    Thanks! Fixed and the build is running.

    March 21st, 2021

  • Photo of Stefan Zweifel 🌿 Stefan Zweifel 🌿 replied

    Never actually looked so closely. Scrolling through the source they: - look at aria-* attributes - listen to keyboard events (esc, arrow-up, arrow-down, etc.) - deal with focus state - fetch remote content and use it as the menu github.com/github/details…

    March 21st, 2021