15

I use navbar component from tailwind ui. It's looks like something like this:

<!-- Profile dropdown -->
<div class="ml-3 relative">
  <div>
    <button
      class="flex text-sm border-2 border-transparent rounded-full focus:outline-none focus:border-white transition duration-150 ease-in-out"
      id="user-menu"
      aria-label="User menu"
      aria-haspopup="true"
    >
      <img
        class="h-8 w-8 rounded-full"
        src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
        alt
      />
    </button>
  </div>
  <!--
  Profile dropdown panel, show/hide based on dropdown state.

  Entering: "transition ease-out duration-100"
    From: "transform opacity-0 scale-95"
    To: "transform opacity-100 scale-100"
  Leaving: "transition ease-in duration-75"
    From: "transform opacity-100 scale-100"
    To: "transform opacity-0 scale-95"
  -->
  <div class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg">
    <div
      class="py-1 rounded-md bg-white shadow-xs"
      role="menu"
      aria-orientation="vertical"
      aria-labelledby="user-menu"
    >
      <a
        href="#"
        class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out"
        role="menuitem"
      >Your Profile</a>
      <a
        href="#"
        class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out"
        role="menuitem"
      >Settings</a>
      <a
        href="#"
        class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out"
        role="menuitem"
      >Sign out</a>
    </div>
  </div>
</div>

In this case when I run this code in vue.js navbar dropdown menu status is open by default. How can set status closed by defaul?

Here is preview:

enter image description here

13 Answers 13

18

I'm not sure if anyone is following this question right now but I'm sharing my solution. In the snippet to the dropdown code there was a comment saying:

            <!--
            Profile dropdown panel, show/hide based on dropdown state.

            Entering: "transition ease-out duration-100"
              From: "transform opacity-0 scale-95"
              To: "transform opacity-100 scale-100"
            Leaving: "transition ease-in duration-75"
              From: "transform opacity-100 scale-100"
              To: "transform opacity-0 scale-95"
          -->

It's basically telling the state of dropdown is changing based on classes names so you'd have to make them dynamic like this:

<div :class="`origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg transition ease-${dropdown ? 'out' : 'in'} duration-${dropdown ? '100' : '75'} transform opacity-${dropdown ? '100' : '0'} scale-${dropdown ? '100' : '95'}`">

now the classes will depend on the dropdown value which is just a property of your component that can be changed via a click event like this:

export default {
  name: 'TheNavBar',
  data() {
    return {
      dropdown: false,
    }
  },
}
<div>
  <button
    id="user-menu"
    class="flex text-sm border-2 border-transparent rounded-full focus:outline-none focus:border-white transition duration-150 ease-in-out"
    aria-label="User menu"
    aria-haspopup="true"
    @click="dropdown = !dropdown"
  >
  </button>
</div>

Sign up to request clarification or add additional context in comments.

1 Comment

This is working fine. But the dropdown menu is not close when we click anywhere outside the button. Is there a way to make it work, so that the dropdown menu will get closed if we click anywhere else on the page?
5

I was using alpinejs and included it using CDN. It worked for me when I put the following in the div that was wrapping the whole component:

x-data="{ open: false }"

The below went into the (User profile image) button div

@click="open = true"

And finally, this went into the drop-down div

x-show="open" @click.away="open = false"

2 Comments

This was it for me! If you are using the HTML version - alpinejs was the best route to go. Thank you! For context I started using a laravel project that was using Tailwind and AlpineJS already. Copying the template from Tailwind didn't work out of box - adding these 3 html tags fixed the issue for me. Thanks friend! Happy coding!
Absolutely perfect for the HTML version, very light solution that doesn't have a big impact on the page load. It should be noted that this doesn't use the documented animations though
4

If the Alpine JS is working fine. Just Copy-paste this code and you will be good to go.

<!-- Profile dropdown -->
<div class="ml-3 relative" x-data="{ dropdown: false }">
  <div>
    <button x-on:click="dropdown = ! dropdown" type="button" class="max-w-xs bg-gray-800 rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white" id="user-menu-button" aria-expanded="false" aria-haspopup="false">
      <span class="sr-only">Open user menu</span>
      <img class="h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
    </button>
  </div>
  <div x-show="dropdown" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
    <!-- Active: "bg-gray-100", Not Active: "" -->
    <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-0">Your Profile</a>

    <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-1">Settings</a>

    <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</a>
  </div>
</div>

Explanation of what I do

I just simply added x-data="{ dropdown: false }" in the parent div to hide the menu at the initial state.

Then, I added x-on:click="dropdown = ! dropdown" on the button to toggle the menu.

And lastly, I added x-show="dropdown" in the menu div. to show or hide the dropdown based on its value.

You can use any text at the spot of dropdown. Because its works like a variable.

Comments

3

I'm exactly using the same component and came here to find an answer :(

Since nobody answered it, here is where I've been: It's explicitly said that you'll need to use Javascript with some Tailwind UI component like this one.

But I've done with no JS, only a CSS tricks and a slightly different markup and a simpler animation (but still smooth! You can see the fiddle on the bottom of this answer).

The markup: I've just removed the div wraper arround the avatar button to benefits from the ~ CSS selector, and I've added an id #user-menu-dropdown:

<!-- Profile dropdown -->
<div class="ml-3 relative">
  <button
        class="flex text-sm border-2 border-transparent rounded-full focus:outline-none focus:border-white transition duration-150 ease-in-out"
        id="user-menu" aria-label="User menu" aria-haspopup="true">
      <img class="h-8 w-8 rounded-full"
             src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
             alt=""/>
  </button>
  <div id="user-menu-dropdown" class="menu-hidden origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg">
    <div
      class="py-1 rounded-md bg-white shadow-xs"
      role="menu"
      aria-orientation="vertical"
      aria-labelledby="user-menu"
    >
      <a
        href="#"
        class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out"
        role="menuitem"
      >Your Profile</a>
      <a
        href="#"
        class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out"
        role="menuitem"
      >Settings</a>
      <a
        href="#"
        class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out"
        role="menuitem"
      >Sign out</a>
    </div>
  </div>
</div>

And here is my LESS

#user-menu ~ #user-menu-dropdown
    @apply transform

    @apply ease-in duration-75 opacity-0 scale-0

#user-menu ~ #user-menu-dropdown:focus-within, #user-menu:focus ~ #user-menu-dropdown
    @apply ease-out duration-100 opacity-100 scale-100

And here is the results, using the generated CSS

Hope it will help you

Comments

3

As pointed out in previous answers one can use Vue.js or some smart CSS to solve the problem. If you don't want to make your page heavy by using Vue or use CSS which has limitations then you can use alpinejs. This is what Tailwindui is using in their demo.

You can use alpinejs either by installing it via yarn or npm or simply install it from CDN.

<script src="https://cdn.jsdelivr.net/gh/alpinejs/[email protected]/dist/alpine.min.js" defer></script>

You don't need to write even one line of javascript. If you installed via npm or yarn then import it to your project.

import "alpinejs";

Open the HTML with navigation code.

Add x-data directive.

<!-- Profile dropdown -->
<div class="ml-3 relative" x-data="open = false">

Now add x-click directive to the element that is clicked to reveal the dropdown.

<button
      class="flex text-sm border-2 border-transparent rounded-full focus:outline-none focus:border-white transition duration-150 ease-in-out"
      id="user-menu"
      aria-label="User menu"
      aria-haspopup="true"
x-on:click="open=true"
    >
      <img
        class="h-8 w-8 rounded-full"
        src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
        alt
      />
    </button>

Finally modify the div that nests the dropdown elements.

<div class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg" x-show="open" x-on:click.away="open = false">

x-on:click.away directive will close the dropdown when mouse is clicked anywhere else.

To polish it off you can use Alpine transition directives to the previous div.

<div class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg" x-show="open" x-on:click.away="open = false" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 -translate-y-1" x-transition:enter-end="opacity-100 translate-y-0" x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100 translate-y-0" x-transition:leave-end="opacity-0 -translate-y-1">

If you have more than two dropdowns then use data directive as under

<div x-data="{openDropdown1:  false, openDropdown2: false}"

Comments

1

Tailwind UI only provides example static html. You'll need to use your JS framework (Vue.js) to dynamically generate similar html that varies based on the state of your app. In general you need to:

1- add a boolean state variable that controls whether the menu is currently displayed or not. Initialize it to false so the menu is hidden by default.

data() {
  return {
    show: false,
  }
}

2- Add a click handler to the menu button to toggle this variable.

<button @click="show = !show">Menu</button>

3- Only render the menu when show is true. You can do this with v-if

<div v-if="show" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg">
  //...menu content
</div>

4- Animate the menu when it gets displayed or hidden. You can do this by wrapping the menu in a Vue.js <transition> component.

<transition
  enter-active-class="transition ease-out duration-100"
  enter-from-class="transform opacity-0 scale-95"
  enter-to-class="transform opacity-100 scale-100"
  leave-active-class="transition ease-in duration-75"
  leave-from-class="transform opacity-100 scale-100"
  leave-to-class="transform opacity-0 scale-95"
>
  <div v-if="show" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg">
    //...menu content
  </div>
</transition>

Here is a full working example in Vue 3: https://codepen.io/mpvosseller/pen/RwoNaVz

Notes:

  • In Vue 2 you should use enter-class and leave-class instead of enter-from-class and leave-from-class
  • In React you can use Tailwind's Headless UI React Transition component for similar functionality.

Comments

1

For anyone struggling with this, the solution is much simpler than all the replies above.

Add this class to the div (so it is invisible when the page first loads): "invisible". Add another class called "profile-menu".

<div class="invisible profile-menu" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">

In Javascript at the bottom of your page add this:

    const navbarBtn = document.querySelector(".close-profile-bar");
    const profileMenu = document.querySelector(".profile-menu");

    navbarBtn.addEventListener("click", () => {
        let results = profileMenu.classList.contains('invisible');
        if(results){
            profileMenu.classList.remove('invisible');
            profileMenu.classList.add('visible');
        } else {
            profileMenu.classList.remove('visible');
            profileMenu.classList.add('invisible');
        }
    });

The above will just remove or add the invisible or invisible class to the profile div.

Comments

1

use v-on:click and v-show

<!-- ProfileNavDropdown.vue -->

<template>

<div class="ml-3 relative">
  <div v-on:click="isActive = !isActive">
    <button
      class="flex text-sm border-2 border-transparent rounded-full focus:outline-none focus:border-white transition duration-150 ease-in-out"
      id="user-menu"
      aria-label="User menu"
      aria-haspopup="true"
    >
      <img
        class="h-8 w-8 rounded-full"
        src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
        alt
      />
    </button>
  </div>
  <!--
  Profile dropdown panel, show/hide based on dropdown state.

  Entering: "transition ease-out duration-100"
    From: "transform opacity-0 scale-95"
    To: "transform opacity-100 scale-100"
  Leaving: "transition ease-in duration-75"
    From: "transform opacity-100 scale-100"
    To: "transform opacity-0 scale-95"
  -->
  <div v-show="isActive" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg">
    <div
      class="py-1 rounded-md bg-white shadow-xs"
      role="menu"
      aria-orientation="vertical"
      aria-labelledby="user-menu"
    >
      <a
        href="#"
        class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out"
        role="menuitem"
      >Your Profile</a>
      <a
        href="#"
        class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out"
        role="menuitem"
      >Settings</a>
      <a
        href="#"
        class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out"
        role="menuitem"
      >Sign out</a>
    </div>
  </div>
</div>
</template>


<script>
export default {
    data: function () {
        return {
            isActive: false,
        }
    },
    
}
</script>

Comments

0

I had this same issue, here is a solution I discovered from my little research:

<!-- vue template -->
<template>
  <div class="relative">
    <button @click="isOpen = !isOpen" class="relative z-10 block h-8 w-8 rounded-full overflow-hidden border-2 border-gray-600 focus:outline-none focus:border-white">
      <img class="h-full w-full object-cover" src="https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=256&q=80" alt="Your avatar">
    </button>
    <button v-if="isOpen" @click="isOpen = false" tabindex="-1" class="fixed inset-0 h-full w-full bg-black opacity-50 cursor-default"></button>
    <div v-if="isOpen" class="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
      <a href="#" class="block px-4 py-2 text-gray-800 hover:bg-indigo-500 hover:text-white">Account settings</a>
      <a href="#" class="block px-4 py-2 text-gray-800 hover:bg-indigo-500 hover:text-white">Support</a>
      <a href="#" class="block px-4 py-2 text-gray-800 hover:bg-indigo-500 hover:text-white">Sign out</a>
    </div>
  </div>
</template>
<script>
//javascript
export default {
  data() {
    return {
      isOpen: false
    }
  },
  created() {
    const handleEscape = (e) => {
      if (e.key === 'Esc' || e.key === 'Escape') {
        this.isOpen = false
      }
    }
    document.addEventListener('keydown', handleEscape)
    this.$once('hook:beforeDestroy', () => {
      document.removeEventListener('keydown', handleEscape)
    })
  }
}
</script>

I hope this helps, you can see more here

Comments

0

Adding to @Andreas Hunter's solution, you may use the more convenient headless-ui (Tailwind CSS) for Vue (click here). It is also available for React. Headless UI is designed to integrate beautifully with Tailwind CSS. Use Menu component for dropdowns:

<template>
  <Menu>
    <MenuButton> More </MenuButton>
    <MenuItems>
      <MenuItem v-slot="{ active }">
        <a :class="{ 'bg-blue-500': active }" href="/account-settings"> Account settings </a>
      </MenuItem>
      <MenuItem v-slot="{ active }">
        <a :class="{ 'bg-blue-500': active }" href="/account-settings"> Documentation </a>
      </MenuItem>
      <MenuItem v-slot="{ active }" disabled>
        <span :class="{ 'bg-blue-500': active }"> Invite a friend (coming soon!) </span>
      </MenuItem>
    </MenuItems>
  </Menu>
</template>

<script>
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'

export default {
  components: {
    Menu,
    MenuButton,
    MenuItems,
    MenuItem,
  },
}
</script>

You don't have to worry about its state anymore.

Comments

0

Since tailwind by default handles the dropdown (open and close, even when you click outside the dropdown). It just programmatically hides the dropdown.

I used a javascript one-line solution by simulating a click on the document body. That way, the dropdown automatically close while you still maintain its animations. And you could simulate a click event on the button/element to open the dropdown.

document.querySelector('body').click()

Comments

0

My vanilla JS solution takes care of the fact that the user might press outside the dropdown and it if so, it should close the dropdown.

1 - Add the class dropdown-toggle & the onclick event to the button HTML:

<button class="dropdown-toggle flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800" onclick="toggleDropdown('user-menu')">

2 - Add the classes invisible dropdown-menu & assign an ID attribute

<div id="user-menu" class="invisible dropdown-menu absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" tabindex="-1">
    <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-0">Your Profile</a>
    <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-1">Settings</a>
    <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</a>
</div>

3 - Create the toggleDropdown function

function toggleDropdown(id)
{
    const element = document.getElementById(id);
    
    if (element.classList.contains('invisible'))
        element.classList.remove('invisible');
    else
        element.classList.add('invisible');
};

4 - Handle the click outside dropdown

window.onclick = function(event)
{
    // Grabs all dropdowns that are currently visible (usually will be only one)
    const elements = document.querySelectorAll('.dropdown-menu:not(.invisible)');

    if (elements.length > 0)
    {
        for (let i = 0; i < elements.length; i++)
        {
            const clickedInsideDropdown = elements[i].contains(event.target);
            
            // Btn toggler is handled in toggleDropdown() function
            const clickedInBtnToggler = event.target.parentElement.classList.contains('dropdown-toggle');

            if (clickedInsideDropdown || clickedInBtnToggler)
                continue;

            elements[i].classList.add('invisible');
        }                
    }
}

Comments

0

Just a common JS solution: I added a ternary operator to the onclick event of the button to make the dropdown menu open and close.

Open user menu

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.