Skip to content
Accessibility: Work in progress
Translations: Not Needed

Dropdown

A highly customizable and extensible dropdown component that supports flat lists, nested tree navigation, search, selection states, and rich option rendering.

Basic Dropdown

The basic dropdown component provides a way to display a list of selectable options in a popup menu. It expects the trigger element to be the default slot.

Here's a simple dropdown with click trigger and basic options:

vue
<template>
  <HLDropdown
    id="basic-dropdown"
    trigger="click"
    placement="bottom"
    :options="simpleOptions"
    :show-search="false"
    width="200"
    @select="handleSelect"
  >
    <HLButton size="sm">Basic Dropdown</HLButton>
  </HLDropdown>
</template>
ts
// Define your options - each must have a unique key and label
const simpleOptions = [
  {
    key: 'edit',
    label: 'Edit Document',
  },
  {
    key: 'share',
    label: 'Share with Team',
  },
  {
    key: 'download',
    label: 'Download as PDF',
  },
  {
    key: 'duplicate',
    label: 'Make a Copy',
  },
  {
    key: 'archive',
    label: 'Archive Document',
  },
  {
    key: 'delete',
    label: 'Delete Document',
  },
]

1. Trigger Types

Choose how users activate the dropdown:

vue
<!-- Click Trigger (Default) -->
<HLDropdown trigger="click" :options="simpleOptions" :show-search="false">
  <HLButton>Click Me</HLButton>
</HLDropdown>

<!-- Hover Trigger -->
<HLDropdown trigger="hover" :options="simpleOptions" :show-search="false">
  <HLButton>Hover Me</HLButton>
</HLDropdown>

<!-- Focus Trigger -->
<HLDropdown trigger="focus" :options="simpleOptions" :show-search="false">
  <HLButton>Focus Me</HLButton>
</HLDropdown>
ts
// Define your options - each must have a unique key and label
const simpleOptions = [
  {
    key: 'edit',
    label: 'Edit Document',
  },
  {
    key: 'share',
    label: 'Share with Team',
  },
  {
    key: 'download',
    label: 'Download as PDF',
  },
  {
    key: 'duplicate',
    label: 'Make a Copy',
  },
  {
    key: 'archive',
    label: 'Archive Document',
  },
  {
    key: 'delete',
    label: 'Delete Document',
  },
]

2. Placement Options

Position your dropdown relative to the trigger element in any of the following directions: top-start, top, top-end, right-start, right, right-end, bottom-start, bottom, bottom-end, left-start, left, left-end

vue
<!-- Different placement examples -->
<HLDropdown id="placement-demo-1" placement="top-start" :options="simpleOptions" :show-search="false" width="200">
    <HLButton size="sm">Opens Top Start</HLButton>
  </HLDropdown>

<HLDropdown id="placement-demo-2" placement="bottom-end" :options="simpleOptions" :show-search="false" width="200">
<HLButton size="sm">Opens Bottom End</HLButton>
</HLDropdown>

<HLDropdown id="placement-demo-3" placement="right" :options="simpleOptions" :show-search="false" width="200">
<HLButton size="sm">Opens Right</HLButton>
</HLDropdown>
ts
// Define your options - each must have a unique key and label
const simpleOptions = [
  {
    key: 'edit',
    label: 'Edit Document',
  },
  {
    key: 'share',
    label: 'Share with Team',
  },
  {
    key: 'download',
    label: 'Download as PDF',
  },
  {
    key: 'duplicate',
    label: 'Make a Copy',
  },
  {
    key: 'archive',
    label: 'Archive Document',
  },
  {
    key: 'delete',
    label: 'Delete Document',
  },
]

3. Width Control

Adapt the dropdown width to your content:

vue
<!-- Fixed width -->
<HLDropdown id="width-demo-1" :width="200" :options="simpleOptions" :show-search="false">
    <HLButton size="sm">Fixed 200px Width</HLButton>
  </HLDropdown>

<!-- Auto width (matches trigger width) -->
<HLDropdown id="width-demo-2" width="auto" :options="simpleOptions" :show-search="false">
<HLButton size="sm" class="w-[200px]">Auto Width</HLButton>
</HLDropdown>
ts
// Define your options - each must have a unique key and label
const simpleOptions = [
  {
    key: 'edit',
    label: 'Edit Document',
  },
  {
    key: 'share',
    label: 'Share with Team',
  },
  {
    key: 'download',
    label: 'Download as PDF',
  },
  {
    key: 'duplicate',
    label: 'Make a Copy',
  },
  {
    key: 'archive',
    label: 'Archive Document',
  },
  {
    key: 'delete',
    label: 'Delete Document',
  },
]

Nested Options

The dropdown component supports nested options, which can be displayed in either a hierarchical cascade or a tree structure.

1. Cascading Dropdown

A cascading dropdown displays options in a linear sequence, where each option's children are displayed as a new dropdown.

vue
<HLDropdown id="nested-dropdown" trigger="click" placement="bottom" :options="nestedOptions" width="200">
    <HLButton size="sm">Nested Dropdown</HLButton>
  </HLDropdown>
ts
const nestedOptions = [
  {
    key: 'produce',
    label: 'Fresh Produce',
    children: [
      {
        key: 'fruits',
        label: 'Fruits & Berries',
        children: [
          {
            key: 'tropical',
            label: 'Tropical Fruits',
            children: [
              { key: 'mango', label: 'Mango', description: 'Sweet and juicy' },
              { key: 'pineapple', label: 'Pineapple', description: 'Tangy and tropical' },
              { key: 'papaya', label: 'Papaya', description: 'Soft and sweet' },
            ],
          },
          {
            key: 'berries',
            label: 'Fresh Berries',
            children: [
              { key: 'strawberry', label: 'Strawberry', description: 'Red and fragrant' },
              { key: 'blueberry', label: 'Blueberry', description: 'Small and antioxidant-rich' },
              { key: 'raspberry', label: 'Raspberry', description: 'Tart and delicate' },
            ],
          },
        ],
      },
      {
        key: 'vegetables',
        label: 'Vegetables',
        children: [
          {
            key: 'leafy',
            label: 'Leafy Greens',
            children: [
              { key: 'spinach', label: 'Spinach', description: 'Dark and nutritious' },
              { key: 'kale', label: 'Kale', description: 'Crispy and healthy' },
              { key: 'lettuce', label: 'Lettuce', description: 'Fresh and crisp' },
            ],
          },
          {
            key: 'root',
            label: 'Root Vegetables',
            children: [
              { key: 'carrot', label: 'Carrot', description: 'Orange and crunchy' },
              { key: 'potato', label: 'Potato', description: 'Starchy and versatile' },
              { key: 'beet', label: 'Beet', description: 'Deep red and earthy' },
            ],
          },
        ],
      },
    ],
  },
  {
    key: 'dairy',
    label: 'Dairy & Eggs',
    children: [
      {
        key: 'milk_products',
        label: 'Milk Products',
        children: [
          {
            key: 'fresh_milk',
            label: 'Fresh Milk',
            children: [
              { key: 'whole_milk', label: 'Whole Milk', description: 'Full fat and creamy' },
              { key: 'reduced_fat', label: '2% Milk', description: 'Reduced fat option' },
              { key: 'skim_milk', label: 'Skim Milk', description: 'Fat-free option' },
            ],
          },
          {
            key: 'yogurt',
            label: 'Yogurt',
            children: [
              { key: 'greek', label: 'Greek Yogurt', description: 'Thick and protein-rich' },
              { key: 'regular', label: 'Regular Yogurt', description: 'Smooth and creamy' },
              { key: 'probiotic', label: 'Probiotic Yogurt', description: 'With live cultures' },
            ],
          },
        ],
      },
      {
        key: 'cheese',
        label: 'Cheese',
        children: [
          {
            key: 'hard_cheese',
            label: 'Hard Cheese',
            children: [
              { key: 'cheddar', label: 'Cheddar', description: 'Sharp and aged' },
              { key: 'parmesan', label: 'Parmesan', description: 'Granular and salty' },
              { key: 'gouda', label: 'Gouda', description: 'Rich and smooth' },
            ],
          },
          {
            key: 'soft_cheese',
            label: 'Soft Cheese',
            children: [
              { key: 'brie', label: 'Brie', description: 'Creamy and mild' },
              { key: 'mozzarella', label: 'Mozzarella', description: 'Fresh and milky' },
              { key: 'camembert', label: 'Camembert', description: 'Rich and buttery' },
            ],
          },
        ],
      },
    ],
  },
]

2. Dropdown Tree

The same options can be displayed in a nested tree structure, which enables hierarchical navigation:

html
<template>
  <HLDropdown id="tree-dropdown" trigger="click" placement="bottom" :options="nestedOptions" tree-mode show-search :width="280">
    <HLButton size="sm">Dropdown Tree</HLButton>
  </HLDropdown>
</template>
ts
const nestedOptions = [
  {
    key: 'produce',
    label: 'Fresh Produce',
    children: [
      {
        key: 'fruits',
        label: 'Fruits & Berries',
        children: [
          {
            key: 'tropical',
            label: 'Tropical Fruits',
            children: [
              { key: 'mango', label: 'Mango', description: 'Sweet and juicy' },
              { key: 'pineapple', label: 'Pineapple', description: 'Tangy and tropical' },
              { key: 'papaya', label: 'Papaya', description: 'Soft and sweet' },
            ],
          },
          {
            key: 'berries',
            label: 'Fresh Berries',
            children: [
              { key: 'strawberry', label: 'Strawberry', description: 'Red and fragrant' },
              { key: 'blueberry', label: 'Blueberry', description: 'Small and antioxidant-rich' },
              { key: 'raspberry', label: 'Raspberry', description: 'Tart and delicate' },
            ],
          },
        ],
      },
      {
        key: 'vegetables',
        label: 'Vegetables',
        children: [
          {
            key: 'leafy',
            label: 'Leafy Greens',
            children: [
              { key: 'spinach', label: 'Spinach', description: 'Dark and nutritious' },
              { key: 'kale', label: 'Kale', description: 'Crispy and healthy' },
              { key: 'lettuce', label: 'Lettuce', description: 'Fresh and crisp' },
            ],
          },
          {
            key: 'root',
            label: 'Root Vegetables',
            children: [
              { key: 'carrot', label: 'Carrot', description: 'Orange and crunchy' },
              { key: 'potato', label: 'Potato', description: 'Starchy and versatile' },
              { key: 'beet', label: 'Beet', description: 'Deep red and earthy' },
            ],
          },
        ],
      },
    ],
  },
  {
    key: 'dairy',
    label: 'Dairy & Eggs',
    children: [
      {
        key: 'milk_products',
        label: 'Milk Products',
        children: [
          {
            key: 'fresh_milk',
            label: 'Fresh Milk',
            children: [
              { key: 'whole_milk', label: 'Whole Milk', description: 'Full fat and creamy' },
              { key: 'reduced_fat', label: '2% Milk', description: 'Reduced fat option' },
              { key: 'skim_milk', label: 'Skim Milk', description: 'Fat-free option' },
            ],
          },
          {
            key: 'yogurt',
            label: 'Yogurt',
            children: [
              { key: 'greek', label: 'Greek Yogurt', description: 'Thick and protein-rich' },
              { key: 'regular', label: 'Regular Yogurt', description: 'Smooth and creamy' },
              { key: 'probiotic', label: 'Probiotic Yogurt', description: 'With live cultures' },
            ],
          },
        ],
      },
      {
        key: 'cheese',
        label: 'Cheese',
        children: [
          {
            key: 'hard_cheese',
            label: 'Hard Cheese',
            children: [
              { key: 'cheddar', label: 'Cheddar', description: 'Sharp and aged' },
              { key: 'parmesan', label: 'Parmesan', description: 'Granular and salty' },
              { key: 'gouda', label: 'Gouda', description: 'Rich and smooth' },
            ],
          },
          {
            key: 'soft_cheese',
            label: 'Soft Cheese',
            children: [
              { key: 'brie', label: 'Brie', description: 'Creamy and mild' },
              { key: 'mozzarella', label: 'Mozzarella', description: 'Fresh and milky' },
              { key: 'camembert', label: 'Camembert', description: 'Rich and buttery' },
            ],
          },
        ],
      },
    ],
  },
]

The dropdown component includes a built-in search functionality that is enabled by default (show-search prop defaults to true). The search feature:

  • Filters through leaf nodes (options without children) in both flat and nested structures
  • Matches case-insensitive text against the option's label property
  • Displays the full path for nested options (e.g., "Parent / Child")
  • Can be disabled by setting show-search to false

For custom search implementation, you can:

  • Use the onSearch prop to provide a custom search handler function
  • Listen to the @search event to implement your own search logic
Vue Template
html
<HLDropdown id="basic-dropdown" trigger="click" placement="bottom" :options="longOptions" width="200" show-search>
  <HLButton size="sm">Basic Dropdown with Search</HLButton>
</HLDropdown>

The dropdown component allows you to implement custom search functionality by handling the @search event. This is useful when you need to perform server-side filtering or apply complex search logic.

html
<template>
  <HLDropdown
    id="custom-search-dropdown"
    trigger="click"
    placement="bottom"
    :options="filteredOptions"
    show-search
    :width="280"
    @search="handleSearch"
  >
    <HLButton size="sm">Custom Search</HLButton>
  </HLDropdown>
</template>
ts
const searchQuery = ref('')
const isLoading = ref(false)

const allOptions = [
  {
    key: 'fruits',
    label: 'Fruits',
    children: [
      { key: 'apple', label: 'Apple', description: 'Red and sweet' },
      { key: 'banana', label: 'Banana', description: 'Yellow and creamy' },
      { key: 'orange', label: 'Orange', description: 'Citrus fruit' },
    ],
  },
  {
    key: 'vegetables',
    label: 'Vegetables',
    children: [
      { key: 'carrot', label: 'Carrot', description: 'Orange and crunchy' },
      { key: 'broccoli', label: 'Broccoli', description: 'Green and healthy' },
      { key: 'potato', label: 'Potato', description: 'Starchy vegetable' },
    ],
  },
]

const filteredOptions = computed(() => {
  if (!searchQuery.value) return allOptions

  const searchLower = searchQuery.value.toLowerCase()

  return allOptions
    .map(group => {
      const matchingChildren = group.children?.filter(
        item => item.label.toLowerCase().includes(searchLower) || item.description?.toLowerCase().includes(searchLower)
      )

      if (!matchingChildren?.length) return null

      return {
        ...group,
        children: matchingChildren,
      }
    })
    .filter(Boolean)
})

let searchTimeout
const handleSearch = value => {
  clearTimeout(searchTimeout)
  isLoading.value = true

  searchTimeout = setTimeout(() => {
    searchQuery.value = value
    isLoading.value = false
  }, 300)
}

Height Limitation

The maxHeight prop can be used to limit the height of the dropdown. It is only applicable when treeMode is enabled.

Vue Template
html
<HLDropdown
  id="tree-dropdown"
  trigger="click"
  placement="bottom"
  :options="longOptions"
  tree-mode
  show-search
  max-height="200px"
  :width="280"
>
  <HLButton size="sm">Dropdown Tree</HLButton>
</HLDropdown>

Option Types

The dropdown component supports various option types, each designed for specific use cases. Below is a concise overview of each type with examples:

  1. Default Text Option - Simple text options with a key and a label.

    ts
    { key: 'default', label: 'Default Option' }
  2. Header - Used to group options together.

    ts
    { key: 'header1', label: 'Group 1', type: 'header' }
  3. Divider - Used to separate options.

    ts
    { key: 'divider1', type: 'divider' }
  4. Avatar - Displays an image.

    ts
    { key: 'avatar1', label: 'User Profile', type: 'avatar', src: 'https://ui-avatars.com/api/?name=John+Doe' }
  5. Icon - Displays an icon. You can also set iconPlacement to place the icon on either side of the label. Note that if you have children, the icon will be placed on the left side by default to accomodate the chevron icon for the children.

    ts
    { key: 'icon1', label: 'Verified Account', type: 'icon', icon: CheckVerified01Icon, iconPlacement: 'left' }
  6. Description with Icon - Displays a description with an icon

    ts
    { key: 'desc1', label: 'Share Post', description: 'Share to social media channels', descriptionIcon: CheckVerified01Icon }
  7. Info Text - Displays additional text to the right of the label

    ts
    { key: 'info1', label: 'Messages', infoText: '5 unread' }
  8. Title Right Slot - Displays custom content on the right side

    ts
    { key: 'slot1', label: 'Performance', titleRightSlot: () => h(HLTag, { size: 'xs', round: true, variant: 'success' }, { default: () => '↑ 10%' }) }
  9. Disabled - Disables an option

    ts
    { key: 'disabled1', label: 'Unavailable Feature', disabled: true }
  10. Search: Displays a search input

    ts
    { key: '__search__', label: 'Search', type: 'search', searchPlaceholder: 'Search options...' }
vue
<template>
  <HLDropdown :options="demoOptions" showSearch treeMode showSelectedMark :closeOnSelect="false" @select="handleDemoSelect">
    <HLButton>{{ demoSelectedValue }}</HLButton>
  </HLDropdown>
</template>
ts

<script setup>
import { CheckVerified01Icon } from '@gohighlevel/ghl-icons/24/outline'
import { h } from 'vue'
import { HLTag } from '@gohighlevel/highrise'

const options = [
  // Default option
  {
    key: 'default',
    label: 'Default Option',
  },

  // Header option
  {
    key: 'header1',
    label: 'Group 1',
    type: 'header',
  },

  // Divider
  {
    key: 'divider1',
    type: 'divider',
  },

  // Avatar option
  {
    key: 'avatar1',
    label: 'User Profile',
    type: 'avatar',
    src: 'https://example.com/avatar.jpg',
  },

  // Icon option with left placement
  {
    key: 'icon1',
    label: 'Verified Account',
    type: 'icon',
    icon: CheckVerified01Icon,
    iconPlacement: 'left',
  },

  // Custom render option
  {
    key: 'render1',
    type: 'render',
    render: () => h('div', { class: 'custom-render' }, 'Custom Rendered Content'),
  },

  // Option with description and icon
  {
    key: 'desc1',
    label: 'Share Post',
    description: 'Share to social media channels',
    descriptionIcon: CheckVerified01Icon,
  },

  // Option with info text
  {
    key: 'info1',
    label: 'Messages',
    infoText: '5 unread',
  },

  // Option with title right slot
  {
    key: 'slot1',
    label: 'Performance',
    titleRightSlot: () => h(HLTag, { size: 'xs', round: true, variant: 'success' }, { default: () => '↑ 10%' }),
  },

  // Disabled option
  {
    key: 'disabled1',
    label: 'Unavailable Feature',
    disabled: true,
  },

  // Nested options (children)
  {
    key: 'parent1',
    label: 'Settings',
    children: [
      {
        key: 'child1',
        label: 'General',
      },
      {
        key: 'child2',
        label: 'Security',
      },
    ],
  },
]
</script>

Scroll Event

Event @scroll is supported in the dropdown component only when we set max-height and treeMode.

html
<template>
  <HLDropdown :options="InfiniteScrollOptions" @scroll="handleScroll" max-height="200px">
    <HLButton size="sm">With Scroll Event</HLButton>
  </HLDropdown>
</template>
<script setup>
  import { HLDropdown, HLButton } from '@platform-ui/highrise'
  import { ref } from 'vue'

  const handleScroll = async event => {
    const scrollPosition = event.target.scrollTop + event.target.clientHeight
    if (scrollPosition >= event.target.scrollHeight - 10) {
      const result = await fetchOptions() // fetch API
      InfiniteScrollOptions.value = [...InfiniteScrollOptions.value, ...result]
    }
  }
  const InfiniteScrollOptions = ref([
    {
      label: 'Option 1',
      key: 'option1',
    },
    {
      label: 'Option 2',
      key: 'option2',
    },
  ])
</script>

Custom Render

The dropdown component supports custom rendering of options using the render type. This is useful for creating highly customized option displays:

WARNING

The @select event is not supported in the render type. You might want to add click handlers to the custom rendered content to handle the selection.

html
<template>
  <HLDropdown id="custom-render-dropdown" trigger="click" placement="bottom" :options="options">
    <HLButton size="sm">Custom Render</HLButton>
  </HLDropdown>
</template>
ts
const options = [

      {
        key: 'complex-custom-content',
        label: 'Complex Custom Content',
        type: 'render',
        render: () => h('div', { class: 'flex flex-col gap-2 p-2' }, [
          h('div', { class: 'flex items-center gap-2' }, [
            h('img', {
              src: 'https://ui-avatars.com/api/?name=John+Doe',
              class: 'w-8 h-8 rounded-full',
              alt: 'Avatar',
            }),
            h('div', { class: 'text-sm font-medium' }, 'John Doe'),
          ]),
          h('div', { class: 'text-xs text-gray-500' }, 'This is a more complex custom rendered option'),
          h('div', { class: 'mt-2 flex items-center gap-2' }, [
            h('div', { class: 'w-2 h-2 rounded-full bg-green-500' }),
            h('span', { class: 'text-xs' }, 'Active'),
            h(HLTag, { size: 'xs', round: true, variant: 'info' }, { default: () => 'New' }),
          ]),
        ])
      },
      {
        key: 'notification',
        label: 'Notification Item',
        type: 'render',
        render: () => h('div', { class: 'flex items-center justify-between p-2 border-b border-gray-100' }, [
          h('div', { class: 'flex items-center gap-3' }, [
            h('div', { class: 'w-2 h-2 rounded-full bg-error-500' }),
            h('div', { class: 'flex flex-col' }, [
              h('div', { class: 'text-sm font-medium' }, 'System Alert'),
              h('div', { class: 'text-xs text-gray-500' }, '2 minutes ago')
            ])
          ]),
          h(HLTag, { size: 'xs', variant: 'error' }, { default: () => 'Urgent' })
        ])
      },
      {
        key: 'user-stats',
        label: 'User Statistics',
        type: 'render',
        render: () => h('div', { class: 'p-2 flex flex-col gap-2' }, [
          h('div', { class: 'flex items-center justify-between' }, [
            h('div', { class: 'text-sm font-medium' }, 'Monthly Stats'),
            h(HLTag, { size: 'xs', variant: 'success', round: true }, { default: () => '↑ 23%' })
          ]),
          h('div', { class: 'flex gap-4 mt-1' }, [
            h('div', { class: 'flex flex-col items-center' }, [
              h('span', { class: 'text-xs font-medium' }, '1.2k'),
              h('span', { class: 'text-xs text-gray-500' }, 'Views')
            ]),
            h('div', { class: 'flex flex-col items-center' }, [
              h('span', { class: 'text-xs font-medium' }, '8.4k'),
              h('span', { class: 'text-xs text-gray-500' }, 'Clicks')
            ]),
            h('div', { class: 'flex flex-col items-center' }, [
              h('span', { class: 'text-xs font-medium' }, '98%'),
              h('span', { class: 'text-xs text-gray-500' }, 'Rating')
            ])
          ])
        ])
      },
      {
        key: 'team-member',
        label: 'Team Member',
        type: 'render',
        render: () => h('div', { class: 'p-2 flex items-center justify-between' }, [
          h('div', { class: 'flex items-center gap-3' }, [
            h('img', {
              src: 'https://ui-avatars.com/api/?name=Sarah+Wilson',
              class: 'w-8 h-8 rounded-full',
              alt: 'Sarah Wilson'
            }),
            h('div', { class: 'flex flex-col' }, [
              h('div', { class: 'text-sm font-medium' }, 'Sarah Wilson'),
              h('div', { class: 'text-xs text-gray-500 flex items-center gap-1' }, [
                h('div', { class: 'w-1.5 h-1.5 rounded-full bg-success-500' }),
                'Online'
              ])
            ])
          ]),
          h('div', { class: 'flex gap-2' }, [
            h(HLTag, { size: 'xs', variant: 'warning' }, { default: () => 'Lead' })
          ])
        ])
      }
    ]"
]

Multiple Selection

The dropdown component supports multiple selection mode, which allows users to select multiple options. You can also control whether the dropdown should close after selection by setting the closeOnSelect prop to false. The showSelectedMark prop can be used to show a checkmark next to the selected option.

html
<template>
  <HLDropdown
    id="multiple-dropdown"
    trigger="click"
    placement="bottom"
    :options="options"
    multiple
    :closeOnSelect="false"
    show-selected-mark
    :width="280"
  >
    <HLButton size="sm">Multiple Selection</HLButton>
  </HLDropdown>
</template>

<script setup>
  const options = [
    {
      key: 'fruits',
      label: 'Fruits',
      children: [
        { key: 'apple', label: 'Apple' },
        { key: 'banana', label: 'Banana' },
        { key: 'orange', label: 'Orange' },
      ],
    },
    {
      key: 'vegetables',
      label: 'Vegetables',
      children: [
        { key: 'carrot', label: 'Carrot' },
        { key: 'broccoli', label: 'Broccoli' },
        { key: 'potato', label: 'Potato' },
      ],
    },
  ]
</script>

Disabled Options

You can disable individual options by setting the disabled key to true on the dropdown option. Disabled options cannot be selected and do not emit the select event when clicked and display a not-allowed cursor on hover.

html
<template>
  <HLDropdown id="disabled-dropdown" trigger="click" placement="bottom" :options="disabledOptions" :width="280">
    <HLButton size="sm">Disabled Options</HLButton>
  </HLDropdown>
</template>
ts
const disabledOptions = [
  {
    key: 'option1',
    label: 'Enabled Option',
    description: 'This option is selectable',
  },
  {
    key: 'option2',
    label: 'Disabled Option',
    description: 'This option cannot be selected',
    disabled: true,
  },
  {
    key: 'group',
    label: 'Mixed Group',
    children: [
      { key: 'enabled', label: 'Enabled Child' },
      { key: 'disabled-child', label: 'Disabled Child', disabled: true },
    ],
  },
]

Disabled Dropdown

You can also disable the entire dropdown by setting the disabled prop on the dropdown component.

WARNING

A disabled dropdown cannot be opened, but the trigger itself remains active. To disable the trigger, it must be explicitly set as disabled.

html
<template>
  <HLDropdown id="disabled-entire-dropdown" trigger="click" placement="bottom" :options="simpleOptions" disabled :width="200">
    <HLButton size="sm">Disabled Dropdown</HLButton>
  </HLDropdown>
</template>

Props

PropTypeDefaultDescription
id *string | undefinedundefinedUnique identifier for the dropdown. Required for accessibility and testing.
optionsDropdownOption[][]Array of options to display in the dropdown menu. See Option Properties for details.
trigger'click' | 'hover' | 'focus''click'Event that triggers the dropdown menu.
placement'top-start' | 'top' | 'top-end' | 'right-start' | 'right' | 'right-end' | 'bottom-start' | 'bottom' | 'bottom-end' | 'left-start' | 'left' | 'left-end''bottom'Position of the dropdown menu relative to the trigger element.
widthnumber | undefined182Width of the dropdown menu in pixels.
showboolean | undefinedundefinedControlled visibility state. Use with v-model:show for two-way binding.
showArrowbooleantrueShows a pointing arrow from the menu to the trigger.
showSelectedMarkbooleanfalseShows a checkmark next to the selected option.
treeModebooleanfalseEnables hierarchical navigation for nested options.
showSearchbooleantrueShows a search input at the top of the dropdown.
multiplebooleanfalseEnables multiple selection mode.
closeOnSelectbooleantrueControls whether the dropdown should close after an option is selected. Selected Mark is not shown if this is set to false.
resetTreeOnChangebooleanfalseControls whether the dropdown tree should reset the tree state when the dropdown is closed.
disabledbooleanfalseDisables the entire dropdown, preventing it from being opened.
maxHeightstring | undefinedundefinedMaximum height of the dropdown menu. Only applicable when tree-mode is enabled.
clearableInSearchbooleanfalseWhether the search input is clearable.
PropertyTypeDescriptionExample
keystringUnique identifier for the option.'edit'
labelstringDisplay text for the option.'Edit Post'
type'default' | 'header' | 'divider' | 'avatar' | 'icon' | 'render' | 'search'Type of menu item to render.'header'
descriptionstringSecondary text shown below the label.'Modify post content'
descriptionIcon() => VNodeIcon rendered next to description.() => h(InfoIcon)
icon() => VNodeIcon for the option.() => h(EditIcon)
iconPlacement'left' | 'right'Position of the icon.'left'
childrenDropdownOption[]Nested options for tree navigation.[{ key: 'sub1', label: 'Sub Option' }]
srcstringImage URL for avatar type options.'/path/to/image.png'
infoTextstringAdditional text shown to the right.'+1'
titleRightSlot() => VNodeCustom content for the right side.() => h(HLTag, { ... })
render() => VNodeCustom render function for the entire option.() => h('div', { ... })
disabledbooleanDisables the option, preventing selection.true
searchPlaceholderstringPlaceholder for the search input.'Search'

Emits

EventArgumentsDescription
@select(key: string | number, option: DropdownOption)Fired when an option is selected.
@update:show(show: boolean)Fired when dropdown visibility changes.
@search(value: string)Fired when search input changes.
@clickoutside(e: MouseEvent | KeyboardEvent | PointerEvent)Fired when user clicks outside of the dropdown.
@scroll(event: Event)Fired when the dropdown is scrolled.

Slots

NameParametersDescription
default()The default content slot