Search code examples
javascripthtmllaravellaravel-bladealpine.js

Alpine JS close menu dropdown on clicking away from element


I'm using Alpine JS as part of a TALL project. I have a navigation menu here, which, for the purposes of this Stackoverflow I've condensed heavily to the barebones. Only one dropdown can be open at any one time, and within a dropdown there would be links to other menu items. All is good.

The part I'm struggling with, is I want all dropdowns to close when clicking away from the current dropdown, you know, away from the div that's open. I still want to be able to toggle a dropdown via the button. But not sure how to achieve the result from within an open dropdown.

Here's my markup

<nav x-data="{ openDropdown: null }">
  <div class="mx-auto h-20 max-w-7xl px-2 sm:px-6 lg:px-8">
    <ul>
      <li x-data="{ id: 'products' }">
        <button x-on:click="openDropdown = openDropdown === id ? null : id" type="button" class="inline-block text-white hover:text-secondary-200 px-3 py-2 text-base font-medium">
          Products
        </button>
        <template x-if="openDropdown === id">
          <div>
            ...
          </div>
        </template>
      </li>
      <li>
        <a href="/" class="inline-block text-white hover:text-secondary-200 px-3 py-2 text-base font-medium">
          Integrations
        </a>
      </li>
      <li x-data="{ id: 'features' }">
        <button x-on:click="openDropdown = openDropdown === id ? null : id" type="button" class="inline-block text-white hover:text-secondary-200 px-3 py-2 text-base font-medium">
          Features
        </button>
        <template x-if="openDropdown === id">
          <div>
            ...
          </div>
        </template>
      </li>
    </ul>
  </div>
</nav>

Solution

  • A simple solution is to use the click.outside event applied to the <ul> element:

    <ul @click.outside="openDropdown = null">
        <li x-data="{ id: 'products' }">
    
        .....
    

    but if you want to close the current dropdown also when you click on the elements without a dropdown, you must reset the openDropdown variable also when clicking on them (unless they open a different page):

    <li @click="openDropdown=null">
    
        <a href="/" class=".....">
            Integrations
        </a>
    
    </li>
    

    Instead of x-if you could use a x-show directive applied to the element to show/hide:

    <li x-data="{ id: 'products' }">
    
        <button @click="openDropdown = openDropdown === id ? null : id"
                type="button"
                class="....."
        >
            Products
        </button>
    
        <div x-show="openDropdown === id">
            .....
        </div>