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
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.
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.
- A button or link which is the users action.
- A card element which will be displayed when the above button/link is clicked or hovered.
- A hidden wrapping
div
which will be acting as a container for the button and the dropdown content
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.
- Import a component called
<dropdown>
in yourindex.vue
page. ```
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.
<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>
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.
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.
Top comments (5)
This is a great tutorial and I love the effort you put into the graphics, also the effort of presenting this in multiple formats.
Quick suggestion, those code blocks would look real nice with some syntax highlighting on em!
That is exactly what I thought and I am planning on building something for this.
Better code embeds
This is neat! I also needed something like "vue-clickaway". Thank you for mentioning it.
I was curious to know what went into the script tag, and how did you configure "vue-clickaway" in Nuxtjs. It would have been better if you also had mentioned the code.</p> <p>A github repo or codesandbox would also have been appreciated. Nonetheless, this also helped me. Thanks!</p>
Great tutorial Fayaz, nice work
Oh nice i love tailwindcss