How to make an animated dropdown with Vue and Tailwindcss
Tags: javascript, codenewbie, webdev, vue
Published: 2020-12-16 16:24:32

There will be a time when you have to make a custom dropdown, I happened to make one recently at work and this is the approach I took.

I will be creating a Nuxt.js project since that's what I am usually preferring over vanilla Vue these days.

Alright, let’s do this.

> TL;DR Here's the [demo](https://tailwind-dropdown.surge.sh/)

---

### 1. Setting up our project.

To create a Nuxt project run `npx create-nuxt-app dropdown` and select Tailwindcss as our choice of framework when setting up the project.

![Creating a new project](https://dev-to-uploads.s3.amazonaws.com/i/3zpf66akz83cj9fcze6n.jpg)

> Nuxt is still in the process of adding the newly released Tailwindcss 2.0, hence we will be installing the v2 manually, in order to that. Let's run a few commands in our project

```
npm i -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
```

This will update the Post css in our nuxt project which is necessary to run the latest version of Tailwind.

Since I won't be changing any of tailwind's config, so I won't create a custom config file, if you want to know how to how to customise tailwindcss.

### 2. How does it work?

So, a dropdown will basically have three things technically.

1. A button or link which is the users action.
2. A card element which will be displayed when the above button/link is clicked or hovered.
3. A hidden wrapping `div` which will be acting as a container for the button and the dropdown content

![How the dropdown works](https://dev-to-uploads.s3.amazonaws.com/i/sstvi8zrz24phvw3vk6e.jpg)

### 3. Making the dropdown

I will be making a vue component, which is better for isolating the dropdown's state, making sure the code is readable and clean.

> PS, you can clear out the boilerplate from index.vue in pages & default.vue in layouts folders resp.

1. Import a component called `<dropdown>` in your `index.vue` page.
```
<template>
  <div class="min-h-screen flex items-center justify-center">
    <dropdown />
  </div>
</template>
```
2. Create a file called dropdown.vue in the components folder.

Now let's add some code, a lot of code actually, but that is what we need when we need a good looking UI tbh, there's not short cuts.

```
<template>
  <div
    class="relative inline-block text-left text-gray-800"
    v-on-clickaway="closeMenu"
  >
    <div>
      <span class="rounded-md shadow-sm">
        <button
          @click="isMenuOpen = !isMenuOpen"
          type="button"
          class="inline-flex items-center justify-between w-full rounded-md border border-gray-300 h-10 px-4 py-4 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-800 transition ease-in-out duration-150 btn-focus"
          id="options-menu"
          aria-haspopup="true"
          aria-expanded="true"
        >
          <span> {{ label }} </span>
          <img
            src="https://s.svgbox.net/hero-solid.svg?ic=chevron-down&fill=grey-800"
            class="-mr-1 ml-2 h-5 w-5"
          />
        </button>
      </span>
    </div>
    <transition
      enter-active-class="transition ease-out duration-100"
      enter-class="transform opacity-0 scale-95"
      enter-to-class="transform opacity-100 scale-100"
      leave-active-class="transition ease-in duration-75"
      leave-class="transform opacity-100 scale-100"
      leave-to-class="transform opacity-0 scale-95"
    >
      <div
        v-if="isMenuOpen"
        class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg text-sm overflow-hidden border z-20"
      >
        <div
          class="rounded-md bg-white shadow-xs"
          role="menu"
          aria-orientation="vertical"
          aria-labelledby="options-menu"
        >
          <div>
            <div class="bg-gray-100 p-4 flex items-center">
              <div class="w-full">
                <img
                  class="h-8 w-8 rounded-full mb-2"
                  src="https://fayazz.co/fayaz.jpg"
                  alt="avatar"
                />
                <p class="font-semibold text-base">Fayaz Ahmed</p>
                <button
                  class="flex items-center justify-between w-full focus:outline-none"
                >
                  <p class="text-gray-600">fayaz@email.com</p>
                  <img
                    src="https://s.svgbox.net/hero-solid.svg?ic=cog&fill=grey-700"
                    class="h-4 w-4"
                  />
                </button>
              </div>
            </div>
          </div>
          <div class="border-t border-gray-100"></div>
          <div class="py-1">
            <nuxt-link to="/" class="p-4 flex items-center space-x-2">
              <img
                src="https://s.svgbox.net/hero-outline.svg?ic=currency-rupee"
                class="h-6 w-6"
              />
              <span> Transaction History </span>
            </nuxt-link>
            <nuxt-link to="/" class="p-4 flex items-center space-x-2">
              <img
                src="https://s.svgbox.net/hero-outline.svg?ic=heart"
                class="h-6 w-6"
              />
              <span> Favourites </span>
            </nuxt-link>
          </div>
          <div class="border-t border-gray-100"></div>
          <div class="py-1">
            <nuxt-link
              to="/"
              @click.native="isMenuOpen = false"
              class="p-4 flex items-center space-x-2"
            >
              <img
                src="https://s.svgbox.net/hero-outline.svg?ic=logout"
                class="h-6 w-6"
              />
              <span> Logout </span>
            </nuxt-link>
          </div>
        </div>
      </div>
    </transition>
  </div>
</template>
```

PS, I have also installed a clickaway plugin for my project called [vue-clickaway](https://www.npmjs.com/package/vue-clickaway). You can just import it as a directive in your component to handle the clicks outside the dropdown and close it.

{% youtube E1vgeCFSRqU %}

Here's what I did to animate the dropdown

I used the vue `transition` component to make it work and these are the tailwind classes, which did the magic.

```
enter-active-class="transition ease-out duration-100"
enter-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100"
leave-active-class="transition ease-in duration-75"
leave-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95"
```

Basically, it's just scaling the dropdown card to **95** and back to **100**.

Let me know if you need any help on this.