Skip to content
RTL Support: Work in progress
Accessibility: Partial
Translations: Not Needed
Migration Guide: Work in progress

Advanced Select

A searchable dropdown select component with rich option rendering, supporting single selection, multiple selection with checkboxes, and tag-based selection. Built as a thin wrapper around the Dropdown component with trigger display via AvatarGroup or InputTag.

Single Select

Select a single option. The trigger displays the selected avatar. Click the selected option again to deselect.

vue
<template>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    @update:value="v => value = v"
  />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref(null)
export const options = [
  { label: 'John Doe', key: '1', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John' },
  { label: 'Jane Smith', key: '2', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Jane' },
  { label: 'Bob Wilson', key: '3', type: 'avatar' },
]

Single Select with Preselected Value

Pass a value to preselect an option on mount.

vue
<template>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    @update:value="v => value = v"
  />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref('1')
export const options = [
  { label: 'John Doe', key: '1', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John' },
  { label: 'Jane Smith', key: '2', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Jane' },
  { label: 'Bob Wilson', key: '3', type: 'avatar' },
]

Multiple Select

Enable multiple to allow selecting several options. The trigger shows an avatar group, and the dropdown shows checkboxes.

vue
<template>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    @update:value="v => value = v"
    multiple
  />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref(['1', '2', '3'])
export const options = [
  { label: 'John Doe', key: '1', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John' },
  { label: 'Jane Smith', key: '2', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Jane' },
  { label: 'Bob Wilson', key: '3', type: 'avatar' },
]

Tags Mode

Set select-type="tags" for tag-based selection. The trigger renders as an InputTag component with closable tags, and the dropdown shows pill-shaped options.

Bug
Feature
vue
<template>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    @update:value="v => value = v"
    select-type="tags"
    trigger-placeholder="Add tags"
    :default-tag-props="{ round: true }"
  />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref(['bug', 'feature'])
export const options = [
  { label: 'Bug', key: 'bug' },
  { label: 'Feature', key: 'feature' },
  { label: 'Documentation', key: 'docs' },
]

Tags with Create

When show-add-tag-c-t-a is true and the search doesn't match any existing option, a "+ tag" button appears to create new tags on the fly.

Add or create tags

vue
<template>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    @update:value="v => value = v"
    select-type="tags"
    :show-add-tag-c-t-a="true"
    :reset-search-string="true"
    trigger-placeholder="Add or create tags"
    search-placeholder="Search or create tags"
  />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref([])
export const options = [
  { label: 'Bug', key: 'bug' },
  { label: 'Feature', key: 'feature' },
  { label: 'Documentation', key: 'docs' },
]

Trigger Placeholder

Use trigger-placeholder to set the hint text shown in the trigger while nothing is selected. Applies to select-type="tags" only.

Choose labels…

vue
<template>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    @update:value="v => value = v"
    select-type="tags"
    trigger-placeholder="Choose labels…"
  />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref([])
export const options = [
  { label: 'Bug', key: 'bug' },
  { label: 'Feature', key: 'feature' },
  { label: 'Documentation', key: 'docs' },
]

Disabled

When disabled is set, the trigger is non-interactive and the dropdown cannot be opened.

vue
<template>
  <HLAdvancedSelect :options="options" :value="value" disabled />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref('1')
export const options = [
  { label: 'John Doe', key: '1', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John' },
  { label: 'Jane Smith', key: '2', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Jane' },
]

Loading State

When loading is true, a skeleton UI fills the dropdown. The skeleton adapts to the mode: avatar + text rows for default, pill shapes for tags.

Default mode

Tags mode

Loading tags...

vue
<template>
  <!-- Default mode -->
  <HLAdvancedSelect :options="options" :value="null" loading />

  <!-- Tags mode -->
  <HLAdvancedSelect :options="options" :value="[]" loading select-type="tags" />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options } from './options'
</script>
ts
export const options = [
  { label: 'John Doe', key: '1', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John' },
  { label: 'Jane Smith', key: '2', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Jane' },
  { label: 'Bob Wilson', key: '3', type: 'avatar' },
]

Dock Selected to Top

When dock-selected-to-top is true, selected options are sorted to the top of the list.

vue
<template>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    @update:value="v => value = v"
    :dock-selected-to-top="dockEnabled"
    multiple
  >
    <template #header>
      <div style="padding: 8px 12px; border-bottom: 1px solid var(--gray-200);">
        <HLCheckbox v-model:checked="dockEnabled" size="sm">Show selected on top</HLCheckbox>
      </div>
    </template>
  </HLAdvancedSelect>
</template>

<script setup lang="ts">
  import { HLAdvancedSelect, HLCheckbox } from '@platform-ui/highrise'
  import { options, value, dockEnabled } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref(['5', '7'])
export const dockEnabled = ref(false)
export const options = [
  { label: 'Charlie Davis', key: '5', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Charlie' },
  { label: 'Eve Johnson', key: '7', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Eve' },
  { label: 'Frank Miller', key: '8', type: 'avatar' },
]

Set show-search to false to hide the search input.

vue
<template>
  <HLAdvancedSelect :options="options" :value="value" @update:value="v => value = v" :show-search="false" />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref(null)
export const options = [
  { label: 'John Doe', key: '1', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John' },
  { label: 'Jane Smith', key: '2', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Jane' },
  { label: 'Bob Wilson', key: '3', type: 'avatar' },
]

Empty State

Customize what shows when the search matches nothing. Use the #empty slot for a static message, or #emptySearch to include the current query via its searchString slot prop. If both are provided, emptySearch wins.

vue
<template>
  <HLAdvancedSelect :options="options" :value="value" @update:value="v => value = v">
    <!-- Access the current query via the slot prop -->
    <template #emptySearch="{ searchString }">
      <div class="empty">No people match “{{ searchString }}”</div>
    </template>

    <!-- Or a static message with #empty -->
    <!-- <template #empty>No results</template> -->
  </HLAdvancedSelect>
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref(null)
export const options = [
  { label: 'John Doe', key: '1', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John' },
  { label: 'Jane Smith', key: '2', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Jane' },
  { label: 'Bob Wilson', key: '3', type: 'avatar' },
]

Virtual Scroll

Enable virtual-scroll for large option lists. Only visible items are rendered for performance.

vue
<template>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    @update:value="v => value = v"
    virtual-scroll
    multiple
    :popover-width="320"
  />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
ts
import { ref } from 'vue'

export const options = Array.from({ length: 200 }, (_, i) => ({
  label: `Option ${i}`,
  key: i,
  type: 'avatar',
}))
export const value = ref([])

Set remote to true to handle search externally. The component emits @search instead of filtering locally; update the options prop with results from your API.

vue
<template>
  <HLAdvancedSelect
    :options="filteredOptions"
    :value="value"
    @update:value="v => value = v"
    :loading="loading"
    remote
    multiple
    @search="onSearch"
  />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { filteredOptions, value, loading, onSearch } from './options'
</script>
ts
import { ref } from 'vue'

// Stand-in for your dataset and API — replace `fetchOptions` with a real request.
const allOptions = [
  { label: 'John Doe', key: '1', type: 'avatar' },
  { label: 'Jane Smith', key: '2', type: 'avatar' },
  { label: 'Bob Wilson', key: '3', type: 'avatar' },
]
const fetchOptions = (query: string) =>
  new Promise<typeof allOptions>(resolve => {
    setTimeout(() => {
      resolve(query ? allOptions.filter(o => o.label.toLowerCase().includes(query.toLowerCase())) : allOptions)
    }, 500)
  })

export const value = ref([])
export const loading = ref(false)
export const filteredOptions = ref(allOptions)

export const onSearch = (query: string) => {
  loading.value = true
  fetchOptions(query).then(results => {
    filteredOptions.value = results
    loading.value = false
  })
}

Scroll Pagination

Listen to @scroll to implement infinite loading. Append new options when the user scrolls near the bottom.

Page: 1 · Total: 10

vue
<template>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    @update:value="v => value = v"
    @scroll="onScroll"
  />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value, onScroll } from './options'
</script>
ts
import { ref } from 'vue'

// Stand-in pagination — replace `fetchPage` with a real API call.
let currentPage = 1
const fetchPage = (page: number) =>
  Array.from({ length: 10 }, (_, i) => ({
    label: `Option ${page}-${i}`,
    key: `page-${page}-${i}`,
    type: 'avatar',
  }))

export const value = ref(null)
export const options = ref(fetchPage(currentPage))

export const onScroll = ({ clientHeight, scrollHeight, scrollTop }) => {
  if (scrollTop + clientHeight >= scrollHeight - 10) {
    currentPage += 1
    options.value = [...options.value, ...fetchPage(currentPage)]
  }
}

Trigger & Popover Display

Several props tune how the trigger and dropdown look without changing behavior:

  • max-avatar-count — how many avatars show in the trigger (default/avatar mode) before collapsing to +N.
  • max-tag-count — the same, for tags mode (number or 'responsive' to fit as many as the width allows).
  • default-avatar-props — props forwarded to every trigger avatar (e.g. { size: 'md' }).
  • placement — where the dropdown opens relative to the trigger.
  • show-arrow — show a pointer arrow from the dropdown to the trigger.
  • max-height — cap the options area height (it scrolls beyond this).
  • popover-container-class — add a CSS class to the dropdown container for custom styling.

Avatar mode · max 3 avatars

Tags mode · max 2 tags

Bug
Feature
+3
vue
<template>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    @update:value="v => value = v"
    multiple
    :max-avatar-count="3"
    :default-avatar-props="{ size: 'md' }"
    placement="bottom-start"
    show-arrow
    max-height="220px"
    popover-container-class="my-select-dropdown"
  />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
vue
<template>
  <!-- max-tag-count: a number, or 'responsive' to fit as many as the width allows -->
  <HLAdvancedSelect
    :options="options"
    :value="value"
    @update:value="v => value = v"
    select-type="tags"
    :max-tag-count="2"
  />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
ts
import { ref } from 'vue'

// Avatar mode
export const value = ref(['1', '2', '3', '4', '5', '6', '7'])
export const options = [
  { label: 'John Doe', key: '1', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John' },
  { label: 'Jane Smith', key: '2', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Jane' },
  { label: 'Bob Wilson', key: '3', type: 'avatar' },
  // ...more people
]

// Tags mode
export const tagValue = ref(['bug', 'feature', 'docs', 'enhancement', 'design'])
export const tagOptions = [
  { label: 'Bug', key: 'bug' },
  { label: 'Feature', key: 'feature' },
  { label: 'Documentation', key: 'docs' },
  { label: 'Enhancement', key: 'enhancement' },
  { label: 'Design', key: 'design' },
]

Controlled Visibility

Bind v-model:show (or pass show + handle @update:show) to control the dropdown open state yourself. The component also emits @close when the dropdown closes.

vue
<template>
  <HLButton size="sm" @click="show = !show">{{ show ? 'Close' : 'Open' }} dropdown</HLButton>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    @update:value="v => value = v"
    v-model:show="show"
    @close="show = false"
  />
</template>

<script setup lang="ts">
  import { HLAdvancedSelect, HLButton } from '@platform-ui/highrise'
  import { options, value } from './options'
  import { ref } from 'vue'

  const show = ref(false)
</script>
ts
import { ref } from 'vue'

export const value = ref('1')
export const options = [
  { label: 'John Doe', key: '1', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John' },
  { label: 'Jane Smith', key: '2', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Jane' },
  { label: 'Bob Wilson', key: '3', type: 'avatar' },
]

Custom Trigger

Use the #trigger slot to render a completely custom trigger element.

vue
<template>
  <HLAdvancedSelect :options="options" :value="value" @update:value="v => value = v">
    <template #trigger="{ disabled }">
      <HLButton variant="primary" :disabled="disabled">
        {{ selectedLabel }}
      </HLButton>
    </template>
  </HLAdvancedSelect>
</template>

<script setup lang="ts">
  import { HLAdvancedSelect, HLButton } from '@platform-ui/highrise'
  import { options, value, selectedLabel } from './options'
</script>
ts
import { ref, computed } from 'vue'

export const value = ref('1')
export const options = [
  { label: 'John Doe', key: '1', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John' },
  { label: 'Jane Smith', key: '2', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Jane' },
  { label: 'Bob Wilson', key: '3', type: 'avatar' },
]
export const selectedLabel = computed(() => options.find(o => o.key === value.value)?.label ?? 'Select...')

Use #header and #footer slots to add custom content above and below the options list.

vue
<template>
  <HLAdvancedSelect :options="options" :value="value" @update:value="v => value = v" multiple>
    <template #header>
      <div>{{ value.length }} selected</div>
    </template>
    <template #footer>
      <HLButton @click="value = []">Clear all</HLButton>
      <HLButton>Apply</HLButton>
    </template>
  </HLAdvancedSelect>
</template>

<script setup lang="ts">
  import { HLAdvancedSelect, HLButton } from '@platform-ui/highrise'
  import { options, value } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref(['1', '2', '3'])
export const options = [
  { label: 'John Doe', key: '1', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John' },
  { label: 'Jane Smith', key: '2', type: 'avatar', src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Jane' },
  { label: 'Bob Wilson', key: '3', type: 'avatar' },
]

Loader Slot

Pair @scroll with the #loader slot to show a spinner pinned at the bottom of the options while the next page loads.

vue
<template>
  <HLAdvancedSelect :options="options" :value="value" @update:value="v => value = v" :popover-width="320" @scroll="onScroll">
    <template #loader>
      <div v-if="loading" class="loader">
        <HLSpin size="sm" />
      </div>
    </template>
  </HLAdvancedSelect>
</template>

<script setup lang="ts">
  import { HLAdvancedSelect, HLSpin } from '@platform-ui/highrise'
  import { options, value, loading, onScroll } from './options'
</script>
ts
import { ref } from 'vue'

// Stand-in pagination — replace `fetchPage` with a real API call.
let currentPage = 1
const fetchPage = (page: number) =>
  Array.from({ length: 10 }, (_, i) => ({
    label: `Option ${page}-${i}`,
    key: `page-${page}-${i}`,
    type: 'avatar',
  }))

export const value = ref(null)
export const loading = ref(false)
export const options = ref(fetchPage(currentPage))

export const onScroll = ({ clientHeight, scrollHeight, scrollTop }) => {
  if (scrollTop + clientHeight >= scrollHeight - 10 && !loading.value) {
    loading.value = true
    currentPage += 1
    setTimeout(() => {
      options.value = [...options.value, ...fetchPage(currentPage)]
      loading.value = false
    }, 600)
  }
}

Custom Option Rendering

Use the #option-renderer slot to fully control how each option is rendered. The slot receives { option } (with option.selected reflecting the current selection) and replaces the default row layout.

The slot replaces the default click handler too — wire @click to your own selection logic and update the bound value yourself.

Default Avatar Type

Hover the avatar group in the trigger to remove selections via the × button; the value flows back through @update:value.

vue
<template>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    multiple
    :show-search="false"
    @update:value="v => value = v"
  >
    <template #option-renderer="{ option }">
      <div
        :style="{
          display: 'flex',
          alignItems: 'center',
          gap: '12px',
          padding: '8px 12px',
          cursor: option.disabled ? 'not-allowed' : 'pointer',
          background: option.selected ? 'var(--primary-50, #eff6ff)' : 'transparent'
        }"
        @click="!option.disabled && toggle(option.key)"
      >
        <HLAvatar :src="option.src" :name="option.label" size="sm" round />
        <div :style="{ flex: 1, minWidth: 0 }">
          <div :style="{ display: 'flex', alignItems: 'center', gap: '6px' }">
            <HLText size="sm" weight="semibold">{{ option.label }}</HLText>
            <HLTag size="xs" :color="roleColor(option.meta?.role)" :bordered="false" :interactive="false" round>
              {{ option.meta?.role }}
            </HLTag>
          </div>
          <HLText size="xs" :style="{ color: 'var(--gray-500)' }">{{ option.description }}</HLText>
        </div>
        <HLBadge v-if="option.meta?.unread" :value="option.meta.unread" type="error" />
      </div>
    </template>
  </HLAdvancedSelect>
</template>

<script setup lang="ts">
import { HLAdvancedSelect, HLAvatar, HLBadge, HLTag, HLText } from '@platform-ui/highrise'
import { options, value, toggle, roleColor } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref(['u-2'])
export const options = [
  { key: 'u-1', label: 'John Doe',   description: '[email protected]',  src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John',  meta: { role: 'Admin',  unread: 3 } },
  { key: 'u-2', label: 'Jane Smith', description: '[email protected]',  src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=Jane',  meta: { role: 'Member', unread: 0 } },
  { key: 'u-3', label: 'Bob Wilson', description: '[email protected]',                                             meta: { role: 'Member', unread: 12 } },
]
export const roleColor = (role: string) => ({ Admin: 'primary', Member: 'gray', Guest: 'warning' }[role] || 'gray')
export const toggle = (key: string) => {
  value.value = value.value.includes(key)
    ? value.value.filter(v => v !== key)
    : [...value.value, key]
}

Tag-mode override with icon and color

When select-type="tags" is set, the component renders pill-shaped options by default. Providing #option-renderer replaces that default — useful for surfacing per-option color or icon metadata.

Feature
vue
<template>
  <HLAdvancedSelect
    :options="options"
    :value="value"
    select-type="tags"
    trigger-placeholder="Pick labels"
    :show-search="false"
    @update:value="v => value = v"
  >
    <template #option-renderer="{ option }">
      <div
        :style="{
          display: 'flex',
          alignItems: 'center',
          gap: '8px',
          padding: '6px 12px',
          cursor: 'pointer',
          background: option.selected ? 'var(--primary-50, #eff6ff)' : 'transparent'
        }"
        @click="toggle(option.key)"
      >
        <span>{{ option.meta?.icon }}</span>
        <HLTag size="xs" :color="option.meta?.color" :bordered="false" :interactive="false" round>
          {{ option.label }}
        </HLTag>
      </div>
    </template>
  </HLAdvancedSelect>
</template>

<script setup lang="ts">
import { HLAdvancedSelect, HLTag } from '@platform-ui/highrise'
import { options, value, toggle } from './options'
</script>
ts
import { ref } from 'vue'

export const value = ref(['feature'])
export const options = [
  { key: 'bug',     label: 'Bug',     meta: { color: 'error',   icon: '🐞' } },
  { key: 'feature', label: 'Feature', meta: { color: 'success', icon: '✨' } },
  { key: 'docs',    label: 'Docs',    meta: { color: 'primary', icon: '📚' } },
]
export const toggle = (key: string) => {
  value.value = value.value.includes(key)
    ? value.value.filter(v => v !== key)
    : [...value.value, key]
}
  • option.selected reflects the current selection state, so use it to drive highlight/check visuals.
  • Wire @click to your own toggle logic — the slot fully replaces the default row, including its built-in click handling.
  • The slot only governs dropdown rows. The trigger (avatar group or input tag) keeps its built-in remove (×) behavior and emits the resulting array via @update:value — always bind that handler so removals from the trigger flow back into your bound value.

Design Guidelines

  • Always set popover-width (a number, or 'trigger' to match the trigger) when virtual-scroll is enabled. Because rows are rendered on demand and measured as they enter the viewport, an unconstrained dropdown will resize horizontally while scrolling as wider rows mount in — set the width up front so the layout stays stable.
vue
<HLAdvancedSelect
  :options="largeOptions"
  virtual-scroll
  :popover-width="320"
/>

Accessibility

  • The dropdown renders with role="menu" and aria-orientation="vertical"
  • Options are role="menuitem" (single) or role="menuitemcheckbox" (multiple), and support Enter/Space to select and Escape to close
  • Disabled options have aria-disabled="true" and are not focusable
  • Multiple mode options have aria-checked reflecting selection state
  • Search input auto-focuses when the dropdown opens

Imports

ts
import { HLAdvancedSelect } from '@platform-ui/highrise'
import type { HLAdvancedSelectOption, HLAdvancedSelectProps } from '@platform-ui/highrise'

Props

NameTypeDefaultDescription
valuestring | number | (string | number)[] | nullundefinedSelected value(s). Use with @update:value or v-model:value.
optionsHLAdvancedSelectOption[][]Array of options. Extends HLDropdownOption with children and meta.
multiplebooleanfalseEnable multiple selection with checkboxes.
selectType'default' | 'tags''default''tags' enables tag-based selection with InputTag trigger.
remotebooleanfalseEmit @search instead of filtering locally.
loadingbooleanundefinedShow skeleton loading UI in dropdown.
showSearchbooleantrueShow or hide the search input.
searchPlaceholderstring'Search'Placeholder for the search input.
triggerPlaceholderstring'Search'Placeholder shown in the trigger when nothing is selected. Only applies to select-type="tags" — the default and multiple triggers render an avatar group with a dashed “add” circle and ignore this prop. Also exposed as the placeholder slot prop on #trigger for custom triggers.
maxAvatarCountnumber5Max avatars in the trigger (default/avatar mode) before +N overflow.
maxTagCountnumber | 'responsive'undefinedMax tags in the trigger (tags mode) before collapsing to +N. 'responsive' fits as many as the width allows.
virtualScrollbooleanfalseEnable virtual scrolling for large lists. Recommended alongside a fixed popoverWidth for a stable dropdown size.
maxHeightstring'320px'Max height of the dropdown options area.
dockSelectedToTopbooleanfalseSort selected options to top.
defaultTagPropsPartial<HLTagProps>undefinedStyling for tags in the InputTag trigger (e.g. { round: true }).
defaultAvatarPropsPartial<HLAvatarProps>undefinedAvatar props for the trigger (e.g. { size: 'md' }).
showbooleanundefinedControlled popover visibility. Omit for uncontrolled.
placementHLPopoverPlacement'bottom-start'Dropdown placement relative to trigger.
showArrowbooleanfalseShow popover arrow.
tostring | HTMLElement | falseundefinedTeleport target for the dropdown.
disabledbooleanfalseDisable the component.
showAddTagCTAbooleantrueShow "+ tag" button when search has no matches (tags mode).
handleNewOptionRemotebooleanfalseEmit @newTag instead of creating the tag locally.
resetSearchStringbooleanfalseClear search when value changes.
popoverWidthnumber | 'trigger'undefinedFixed width or match trigger width. Tags mode always matches trigger.
popoverContainerClassstringundefinedAdditional CSS class on the dropdown container.
idstringundefinedUnique identifier for the component.

Emits

NameParametersDescription
@update:value(value: string | number | (string | number)[] | null)Selection changed.
@update:show(show: boolean)Popover visibility changed.
@search(query: string)Search input changed (only when remote is true).
@scroll(event: { clientHeight: number; scrollHeight: number; scrollTop: number })Options area scrolled. Use for infinite loading.
@close()Dropdown closed.
@newTag(label: string)New tag requested (when handleNewOptionRemote is true).

Slots

NamePropsDescription
trigger{ disabled, value, placeholder }Custom trigger element.
header-Sticky content above the search/options.
footer-Sticky content below the options.
empty-Shown when search has no results.
emptySearch{ searchString }Alternative to empty with access to the search query.
option-renderer{ option }Custom rendering for each option.
loader-Content at the bottom of options list (for infinite scroll).

Best Practices

  1. Always use key as the unique identifier for options (inherited from HLDropdownOption)
  2. Set type: 'avatar' on options that should render with an avatar (image or initials fallback)
  3. Use remote mode for server-side filtering — update options with API results on @search
  4. Use virtual-scroll for lists with more than 50 options
  5. Prefer #header and #footer slots for consistent sticky positioning
  6. Use dock-selected-to-top with multiple mode for better UX with many options