How to build a dropdown in Vue

Dropdown components provide collapsible menus for navigation, actions, or selections, essential for space-efficient user interfaces and organized content. As the creator of CoreUI, a widely used open-source UI library, I’ve built dropdown systems in Vue applications throughout my 11 years of Vue development. The most maintainable approach is creating a reusable dropdown component with Composition API, managing open state and click-outside detection. This method ensures consistent dropdown behavior across applications with accessibility features and proper event handling.

Create a dropdown component with toggle state and click-outside detection for automatic closing.

<template>
  <div class="dropdown" ref="dropdownRef">
    <button @click="isOpen = !isOpen" class="dropdown-toggle">
      {{ label }}
      <span :class="{ rotated: isOpen }"></span>
    </button>
    <div v-if="isOpen" class="dropdown-menu">
      <slot></slot>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const props = defineProps({
  label: {
    type: String,
    default: 'Dropdown'
  }
})

const isOpen = ref(false)
const dropdownRef = ref(null)

const handleClickOutside = (event) => {
  if (dropdownRef.value && !dropdownRef.value.contains(event.target)) {
    isOpen.value = false
  }
}

onMounted(() => {
  document.addEventListener('click', handleClickOutside)
})

onUnmounted(() => {
  document.removeEventListener('click', handleClickOutside)
})
</script>

<style scoped>
.dropdown {
  position: relative;
  display: inline-block;
}

.dropdown-toggle {
  padding: 0.5rem 1rem;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.dropdown-menu {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 0.25rem;
  background: white;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  min-width: 200px;
  z-index: 1000;
}

.rotated {
  transform: rotate(180deg);
  transition: transform 0.2s;
}
</style>

Here the dropdown-toggle button toggles isOpen state on click. The v-if directive conditionally renders the dropdown-menu based on isOpen. The handleClickOutside function checks if clicks occur outside the dropdown element using contains(), closing the dropdown when clicking elsewhere. The dropdownRef template ref accesses the dropdown DOM element. Event listeners attach on component mount and clean up on unmount to prevent memory leaks. The slot allows flexible dropdown content.

Best Practice Note:

This is the dropdown implementation we use in CoreUI Vue components for navigation menus and action lists. Add keyboard navigation with arrow keys and Enter for accessibility, implement focus trapping to keep tab navigation within open dropdowns, and use Teleport to render dropdowns at body level if parent containers have overflow: hidden restrictions.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author