Tutorials 4 min read

How to create a flyout menu with Tailwind CSS: JavaScript and Alpine.js

Build a flyout menu (a dropdown) with Tailwind CSS, two ways: vanilla JavaScript class toggling or Alpine.js with x-show and smooth transitions.

Today we are building a simple flyout menu, AKA a dropdown. We’ll do it twice, first with vanilla JavaScript, then with Alpine.js, so you can pick whichever fits your stack.

What is a flyout menu?

A flyout menu is a menu that appears when the user clicks a button or icon, typically used to display additional information or options related to a specific item or action.

Use cases

  • Navigation menus: reveal sub-menus when a user clicks a specific item.
  • Product listings: show extra details or actions for a product or category.
  • Forms: tuck away secondary fields or options until they are needed.
  • Tooltips: offer contextual information next to the element that triggered it.

The markup

  • id="flyoutButton": the button that toggles the flyout menu.
  • id="flyoutMenu": the element that contains the flyout menu.
  • absolute: positions the menu relative to its nearest positioned ancestor.
  • z-10: keeps the menu on top of other elements.
  • max-w-xs: caps the width so the menu fits within the screen.
  • hidden: the class the script toggles to show and hide the menu.

Classes and irrelevant content are removed for brevity, but I’ll keep those classes relevant to the tutorial.

html
<!-- Button to toggle the dropdown menu visibility -->
<button id="flyoutButton">I am your flyout menu</button>
<!-- Dropdown Menu -->
<div id="flyoutMenu" class="absolute z-10 hidden max-w-xs ">
  <!-- Dr<div>
      opdown content goes here -->
</div>

The script

  • document.addEventListener("DOMContentLoaded", function () {: runs when the page is loaded, then grabs the button and the menu.
  • button.addEventListener("click", function (event) {: toggles the menu when the button is clicked.
  • event.stopPropagation();: stops the click from bubbling up, so the document listener doesn’t immediately close the menu.
  • menu.classList.toggle("hidden");: shows or hides the menu.
  • The document click listener adds hidden back whenever a click lands outside the menu.
js
document.addEventListener("DOMContentLoaded", function () {
  const button = document.getElementById("flyoutButton");
  const menu = document.getElementById("flyoutMenu");
  button.addEventListener("click", function (event) {
    event.stopPropagation(); // Prevent click event from bubbling up
    menu.classList.toggle("hidden");
  });
  document.addEventListener("click", function (event) {
    if (!menu.contains(event.target)) {
      menu.classList.add("hidden");
    }
  });
});

The Alpine.js version

Same component, no separate script: the state lives in x-data.

  • x-data="{ open: false }": initializes the component with a reactive open property that tracks whether the dropdown is visible.
  • @click="open = !open" on the button: toggles open, showing or hiding the menu.
  • @click.away="open = false": listens for clicks outside the wrapper and closes the dropdown.
  • x-show="open": toggles the visibility of the menu based on the state.
html
<div
  @click.away="open = false"
  class="relative flex"
  x-data="{ open: false }"
></div>

Alpine also makes it easy to animate the menu in and out with x-transition:

  • x-transition:enter="transition ease-out duration-100": the enter transition, 100 milliseconds with an ease-out timing.
  • x-transition:enter-start="transform opacity-0 scale-95" and x-transition:enter-end="transform opacity-100 scale-100": the menu starts slightly scaled down and transparent, then grows to full size and opacity.
  • x-transition:leave="transition ease-in duration-75" with its leave-start and leave-end values: the same in reverse, fading and scaling the menu out over 75 milliseconds.
html
<div
  x-show="open"
  x-transition:enter="transition ease-out duration-100"
  x-transition:enter-start="transform opacity-0 scale-95"
  x-transition:enter-end="transform opacity-100 scale-100"
  x-transition:leave="transition ease-in duration-75"
  x-transition:leave-start="transform opacity-100 scale-100"
  x-transition:leave-end="transform opacity-0 scale-95"
  class="...."
></div>

And the Tailwind classes that position the dropdown:

  • absolute z-10: positions the menu absolutely and keeps it above other content.
  • w-screen max-w-xs px-2 mt-3: sizes the menu and spaces it away from the toggle button.
  • transform -translate-x-1/2 left-1/2: centers the dropdown horizontally relative to its parent.
html
<div
  x-show="open"
  x-transition:enter="..."
  x-transition:enter-start="..."
  x-transition:enter-end="..."
  x-transition:leave="..."
  x-transition:leave-start="..."
  x-transition:leave-end="..."
  class="absolute z-10 w-screen max-w-xs px-2 mt-3 transform -translate-x-1/2 left-1/2"
></div>

You can try the Alpine version separately: live demo and source code.

Which one should you use?

If Alpine is already in your project, x-show with x-transition gives you the click-away handling and smooth animations for free. If not, the vanilla version is a few lines and no dependency.

Conclusion

This is a simple flyout menu and a great starting point for building more complex ones; check out the other tutorials for more advanced examples.

Hope you enjoyed this tutorial and have a great day!

/Michael Andreuzza