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 Usage

Vue Template
vue
<template>
  <HLDropdown id="basic-dropdown" trigger="click" placement="bottom" :options="simpleOptions" :show-search="false" width="200">
    <HLButton size="sm">Basic Dropdown</HLButton>
  </HLDropdown>
</template>
<script setup lang="ts">
import { HLDropdown, HLButton } from '@platform-ui/highrise'

const simpleOptions = [
  {
    key: 'option1',
    label: 'option1',
  },
  {
    key: 'option2',
    label: 'option2',
  },
  {
    key: 'option3',
    label: 'option3',
  },
]
</script>
Vue Template
vue
<HLDropdown id="basic-dropdown" trigger="click" placement="bottom" :options="simpleOptions" width="200">
    <HLButton size="sm">Basic Dropdown with Search</HLButton>
      </HLDropdown>

Close on Select

Vue Template
vue
<HLDropdown

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

vue
<template>
  <HLDropdown id="tree-dropdown" trigger="click" placement="bottom" :options="options" tree-mode show-search :width="280">
    <HLButton size="sm">Dropdown Tree</HLButton>
  </HLDropdown>
</template>
ts
const options = [
  {
    key: 'locations',
    label: 'Locations',
    children: [
      {
        key: 'usa',
        label: 'USA',
        type: 'avatar',
        src: 'path/to/usa-flag.png',
        infoText: '+1',
      },
      {
        key: 'india',
        label: 'India',
        type: 'avatar',
        src: 'path/to/india-flag.png',
        infoText: '+91',
      },
    ],
  },
]

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.

vue
<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)
}

Custom Render

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

vue
<template>
  <HLDropdown id="custom-render-dropdown" trigger="click" placement="bottom" :options="options">
    <HLButton size="sm">Custom Render</HLButton>
  </HLDropdown>
</template>
ts
const options = [
  {
    key: 'custom-content',
    label: 'Custom Content',
    type: 'render',
    render: () =>
      h(
        'div',
        {
          class: 'flex flex-col gap-2 p-2',
        },
        [
          h('div', { class: 'text-sm font-medium' }, 'Custom Content'),
          h('div', { class: 'text-xs text-gray-500' }, 'This is a fully 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'),
          ]),
        ]
      ),
  },
]

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.

vue
<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>

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.
closeOnSelectbooleanfalseControls whether the dropdown should close after an option is selected.
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', { ... })

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 )

Slots

NameParametersDescription
default()The default content slot