Skip to content
RTL Support: Full
Accessibility: Full
Translations: Not Needed

Select

Select component for choosing single or multiple options

Default Select

Single-select dropdown bound to a flat array of options.

Please Select
Vue
vue
<template>
  <HLSelect :options="simpleSelectOptions" :value="selectedValue" @update:value="handleSimpleChange" />
</template>
<script setup lang="ts">
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'

  const selectedValue = ref(null)
  const simpleSelectOptions = [
    {
      label: 'Option 1',
      value: 'option1',
    },
    {
      label: 'Option 2',
      value: 'option2',
    },
    {
      label: 'Option 3',
      value: 'option3',
    },
  ]

  const handleSimpleChange = value => {
    selectedValue.value = value
  }
</script>

With Icon

Renders a leading icon in the trigger via the icon slot with type="avatar".

Please Select
vue
<template>
  <HLSelect type="avatar" :options="options" :value="selectedValue" @update:value="handleChange">
    <template #icon>
      <div class="hr-select-menu-placeholder-icon">
        <User01Icon />
      </div>
    </template>
  </HLSelect>
</template>
<script setup lang="ts">
  import { HLSelect } from '@platform-ui/highrise'
  import { User01Icon } from '@gohighlevel/ghl-icons/24/outline'
  import { ref } from 'vue'
  import { options } from './options'

  const selectedValue = ref('')
  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
export const options = [
  {
    type: 'group',
    label: 'Rubber Soul',
    key: 'Rubber Soul',
    children: [
      {
        label: 'Drive My Car',
        value: 'song1',
        description: 'Drive My Car.ogg',
        tagColor: 'blue',
      },
      {
        label: 'Norwegian Wood',
        value: 'song2',
        description: 'Norwegian Wood.ogg',
        tagColor: 'green',
      },
      {
        label: "Everybody's Got Something to Hide Except Me and My Monkey",
        value: 'song0',
        description:
          'lorsum ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec purus feugiat, molestie ipsum et, consequat nunc. Nulla facilisi. Nullam. Loresum ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec purus feugiat, molestie ipsum et, consequat nunc. Nulla facilisi. Nullam.lorsum ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec purus feugiat, molestie ipsum et, consequat nunc. Nulla facilisi. Nullam. Loresum ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec purus feugiat, molestie ipsum et, consequat nunc. Nulla facilisi. Nullam.',
        tagColor: 'blue',
      },
      // ... other songs
    ],
  },
  {
    type: 'group',
    label: 'Let It Be',
    key: 'Let It Be Album',
    children: [
      {
        label: 'Two Of Us',
        value: 'Two Of Us',
        tagColor: 'purple',
      },
      {
        label: 'Dig A Pony',
        value: 'Dig A Pony',
        description: 'Dig A Pony.avi',
        tagColor: 'orange',
      },
      // ... other songs
    ],
  },
]

Searchable Select

Enables in-dropdown text filtering with filterable and a search icon via showSearchIcon.

vue
<template>
  <HLSelect filterable showSearchIcon :options="options" :value="selectedValue" @update:value="handleChange" />
</template>
<script setup lang="ts">
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'
  import { options } from './options'

  const selectedValue = ref('')
  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
export const options = [
  {
    type: 'group',
    label: 'Rubber Soul',
    key: 'Rubber Soul',
    children: [
      {
        label: 'Drive My Car',
        value: 'song1',
        description: 'Drive My Car.ogg',
        tagColor: 'blue',
      },
      {
        label: 'Norwegian Wood',
        value: 'song2',
        description: 'Norwegian Wood.ogg',
        tagColor: 'green',
      },
      {
        label: "Everybody's Got Something to Hide Except Me and My Monkey",
        value: 'song0',
        description:
          'lorsum ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec purus feugiat, molestie ipsum et, consequat nunc. Nulla facilisi. Nullam. Loresum ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec purus feugiat, molestie ipsum et, consequat nunc. Nulla facilisi. Nullam.lorsum ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec purus feugiat, molestie ipsum et, consequat nunc. Nulla facilisi. Nullam. Loresum ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec purus feugiat, molestie ipsum et, consequat nunc. Nulla facilisi. Nullam.',
        tagColor: 'blue',
      },
      // ... other songs
    ],
  },
  {
    type: 'group',
    label: 'Let It Be',
    key: 'Let It Be Album',
    children: [
      {
        label: 'Two Of Us',
        value: 'Two Of Us',
        tagColor: 'purple',
      },
      {
        label: 'Dig A Pony',
        value: 'Dig A Pony',
        description: 'Dig A Pony.avi',
        tagColor: 'orange',
      },
      // ... other songs
    ],
  },
]

Empty Options State

Use an empty array to render the built-in empty state while keeping the trigger interactive.

No options available
Vue
vue
<template>
  <HLSelect
    id="select-empty-options"
    :options="[]"
    placeholder="No options available"
    aria-label="Empty select example"
  />
</template>
<script setup lang="ts">
  import { HLSelect } from '@platform-ui/highrise'
</script>

Loading

Set loading to show a loading indicator in the select.

Please Select
Vue
vue
<template>
  <HLSelect :options="options" :loading="true" />
</template>
<script setup lang="ts">
  import { HLSelect } from '@platform-ui/highrise'

  const options = [
    { label: 'Option 1', value: 'option1' },
    { label: 'Option 2', value: 'option2' },
    { label: 'Option 3', value: 'option3' },
  ]
</script>

Clearing the value

When you want to clear the value of the select, you can set the value to null. Setting the value to undefined will create inconsistent behavior.

Please Select
Vue
vue
<template>
  <HLSelect :options="options" v-model:value="selectedValue2" />
  <HLButton @click="selectedValue2 = null">Clear Value</HLButton>
</template>
<script setup lang="ts">
  import { HLSelect, HLButton } from '@platform-ui/highrise'
  import { ref } from 'vue'

  const selectedValue2 = ref('')
  const options = ref([
    { label: 'Option 1', value: 'option1' },
    { label: 'Option 2', value: 'option2' },
    { label: 'Option 3', value: 'option3' },
  ])
</script>

Multiple Selection

Allows selecting multiple values, rendering each choice as a removable tag.

Please Select
vue
<template>
  <HLSelect
    multiple
    type="avatar"
    filterable
    showSearchIcon
    :options="options"
    :value="selectedValue"
    :showAvatarInTags="false"
    @update:value="handleChange"
    id="select-multiple"
  />
</template>
<script setup lang="ts">
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'
  import { options } from './options'

  const selectedValue = ref('')
  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
export const options = [
  {
    type: 'group',
    label: 'Rubber Soul',
    key: 'Rubber Soul',
    children: [
      {
        label: 'Drive My Car',
        value: 'song1',
        description: 'Drive My Car.ogg',
        tagColor: 'blue',
      },
      {
        label: 'Norwegian Wood',
        value: 'song2',
        description: 'Norwegian Wood.ogg',
        tagColor: 'green',
      },
      {
        label: "Everybody's Got Something to Hide Except Me and My Monkey",
        value: 'song0',
        description:
          'lorsum ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec purus feugiat, molestie ipsum et, consequat nunc. Nulla facilisi. Nullam. Loresum ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec purus feugiat, molestie ipsum et, consequat nunc. Nulla facilisi. Nullam.lorsum ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec purus feugiat, molestie ipsum et, consequat nunc. Nulla facilisi. Nullam. Loresum ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec purus feugiat, molestie ipsum et, consequat nunc. Nulla facilisi. Nullam.',
        tagColor: 'blue',
      },
      // ... other songs
    ],
  },
  {
    type: 'group',
    label: 'Let It Be',
    key: 'Let It Be Album',
    children: [
      {
        label: 'Two Of Us',
        value: 'Two Of Us',
        tagColor: 'purple',
      },
      {
        label: 'Dig A Pony',
        value: 'Dig A Pony',
        description: 'Dig A Pony.avi',
        tagColor: 'orange',
      },
      // ... other songs
    ],
  },
]

Multiple Selection without Avatar

Multi-select with plain text tags by setting showAvatarInTags to false.

Please Select
vue
<template>
  <HLSelect
    multiple
    filterable
    showSearchIcon
    :options="simpleMultiSelectOptions"
    :value="selectedValue"
    @update:value="handleSimpleMultiChange"
    id="select-multiple"
    :showAvatarInTags="false"
  />
</template>
<script setup lang="ts">
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'
  import { simpleMultiSelectOptions } from './options'

  const selectedValue = ref('')
  const handleSimpleMultiChange = value => {
    selectedValue.value = value
  }
</script>
ts
export const simpleMultiSelectOptions = [
  {
    label: 'Option 1 with a long label to test the overflow',
    value: 'option1',
  },
  {
    label: 'Option 2 with a long label to test the overflow',
    value: 'option2',
  },
  {
    label: 'Option 3 with a long label to test the overflow',
    value: 'option3',
  },
  {
    label: 'Option 4',
    value: 'option4',
  },
  {
    label: 'Option 5',
    value: 'option5',
  },
  {
    label: 'Option 6',
    value: 'option6',
  },
]

Tag Truncation

When using multiple selection with long tag labels, you can enable tag truncation to prevent tags from overflowing their container. This is particularly useful when dealing with lengthy option labels.


Please Select
vue
<template>
  <HLSelect
    multiple
    filterable
    showSearchIcon
    :allowTagTruncation="true"
    :maxTagWidth="120"
    :options="options"
    :value="selectedValue"
    @update:value="handleChange"
    id="select-truncated"
    :showAvatarInTags="false"
  />
</template>
<script setup lang="ts">
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'
  import { options } from './options'

  const selectedValue = ref('')
  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
export const options = [
  {
    label: 'Option 1 with a very long label that would normally overflow',
    value: 'option1',
  },
  {
    label: 'Option 2 with another extremely long label for testing truncation',
    value: 'option2',
  },
  {
    label: 'Option 3 with yet another long label to demonstrate truncation behavior',
    value: 'option3',
  },
  {
    label: 'Short option',
    value: 'option4',
  },
]

Use remote search when you need to fetch options dynamically from an API or when dealing with large datasets that should be filtered server-side.

INFO

remote requires filterable to be set as well. The @search event (which you handle to fetch options) only fires while the search input is active, and that input is only rendered when filterable is true. Handle @search to load options and drive the loading state; the component does not filter locally in remote mode.


vue
<template>
  <HLSelect
    id="remote-search"
    :value="selectedValue"
    :options="filteredOptions"
    :loading="loading"
    :filterable="true"
    :remote="true"
    placeholder="Type to search..."
    aria-label="Remote search select example"
    @search="handleSearch"
    @update:value="handleChange"
  />
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import { HLSelect } from '@platform-ui/highrise'

  const selectedValue = ref(null)
  const loading = ref(false)
  const filteredOptions = ref([])

  const handleSearch = async query => {
    loading.value = true

    try {
      // Simulate API delay
      await new Promise(resolve => setTimeout(resolve, 1000))

      // Filter results if there's a query, otherwise show all
      filteredOptions.value =
        query && query.trim()
          ? InfiniteScrollOptions.value.filter(option => option.label.toLowerCase().includes(query.toLowerCase().trim()))
          : InfiniteScrollOptions.value
    } catch (error) {
      console.error('Search failed:', error)
      filteredOptions.value = []
    } finally {
      loading.value = false
    }
  }

  // Load initial options
  handleSearch('')

  const handleChange = (value, option) => {
    selectedValue.value = value
    console.log('Selected:', { value, option })
  }
</script>

Infinite Scroll

Appends more options as the user scrolls to the bottom via the scroll event, keeping the menu open with reset-menu-on-options-change="false".

Please Select
Vue
vue
<template>
  <HLSelect
    :options="InfiniteScrollOptions"
    :value="infiniteSelectedValue"
    @update:value="handleInfiniteChange"
    @scroll="handleScroll"
    :reset-menu-on-options-change="false"
    :loading="infiniteLoading"
  />
</template>
<script setup lang="ts">
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'

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

Advanced Tag Configuration with tagProps

The new tagProps object provides complete control over tag rendering in multiple selection mode.

Basic Usage

Each option carries its own tagProps, so tags in the same select can differ in color, shape, and behaviour. Note the disabled tag has no close button, and the third tag truncates at maxWidth.

ST

Success Tag

IT

Interactive Tag
3

TT

Truncated Tag with a very long label

DT

Disabled Tag
vue
<template>
  <HLSelect multiple :options="options" :value="selectedValue" @update:value="handleChange" />
</template>

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

  const selectedValue = ref(['option1', 'option2', 'option3', 'option4'])

  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
export const options = [
  {
    label: 'Success Tag',
    value: 'option1',
    tagProps: {
      color: 'success',
      round: true,
    },
  },
  {
    label: 'Interactive Tag',
    value: 'option2',
    tagProps: {
      color: 'primary',
      interactive: true,
      count: 3,
    },
  },
  {
    label: 'Truncated Tag with a very long label',
    value: 'option3',
    tagProps: {
      color: 'blue',
      truncate: true,
      maxWidth: 120,
    },
  },
  {
    label: 'Disabled Tag',
    value: 'option4',
    tagProps: {
      color: 'gray',
      disabled: true,
      closable: false,
    },
  },
]

Available tagProps Options

PropertyTypeDescriptionDefault
colorHLTagColorTag color variant'gray'
size'lg' | 'md' | 'sm' | 'xs'Tag sizeBased on select size
roundbooleanRounded appearancefalse
borderedbooleanShow bordertrue
interactivebooleanInteractive statestrue
disabledbooleanDisabled statefalse
closablebooleanShow close buttontrue
countnumberDisplay count in tagundefined
dropdown'open' | 'close' | falseDropdown indicatorfalse
truncatebooleanTruncate long textfalse
maxWidthstring | numberMax width for truncation136
avatarPropsHLAvatarPropsAvatar configurationundefined

Avatar Configuration

The avatarProps property customizes the avatar shown inside each tag. Pass src for an image, or name to fall back to initials.

INFO

Tag avatars only render when the select has type="avatar" and showAvatarInTags is left true (the default). With the default type="default", avatarProps is ignored.

John Doe

JS

Jane Smith
vue
<template>
  <!-- type="avatar" is required for tag avatars to render -->
  <HLSelect multiple type="avatar" :options="options" :value="selectedValue" @update:value="handleChange" />
</template>

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

  const selectedValue = ref(['user1', 'user2'])

  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
export const options = [
  {
    label: 'John Doe',
    value: 'user1',
    tagProps: {
      color: 'primary',
      avatarProps: {
        size: 'xs', // Avatar size
        objectFit: 'cover', // How the image fits in the avatar
        round: true, // Rounded avatar
        src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=John', // Avatar image URL
      },
    },
  },
  {
    // No src — the avatar falls back to initials derived from `name`
    label: 'Jane Smith',
    value: 'user2',
    tagProps: {
      color: 'blue',
      avatarProps: {
        size: 'xs',
        round: true,
        name: 'Jane Smith',
      },
    },
  },
]

INFO

name is only used when avatarProps.src is absent — if you pass both, src wins and name is dropped. When neither is set, the avatar falls back to the option's label.

Migration from tagColor

The tagColor prop is deprecated but still supported for backward compatibility:

js
// Old way (deprecated but still works)
{
  label: "Option",
  value: "opt1",
  tagColor: "primary"
}

// New way (recommended)
{
  label: "Option",
  value: "opt1",
  tagProps: {
    color: "primary"
  }
}

Teleport Select

By default, the select dropdown menu is rendered within its parent component's DOM tree. However, this can cause styling and positioning issues, especially when the select is used inside containers with overflow: hidden, fixed positioning, or complex z-index stacking contexts (like modals, dialogs, or fixed sidebars).

The to prop allows you to teleport (move) the dropdown menu to a different location in the DOM tree, outside of its parent component. This ensures the dropdown remains visible and correctly positioned, regardless of its parent container's styling constraints.

You can specify the target location using:

  • A CSS selector (e.g., to="#select-teleport-target")
  • false to disable teleporting
  • true to teleport to the default location (body)

Please Select
Vue
vue
<template>
  <div id="select-teleport-target"></div>
  <HLSelect :options="options" :value="selectedValue" @update:value="handleChange" to="#select-teleport-target" id="select-teleport" />
</template>
<script setup lang="ts">
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'

  const selectedValue = ref(null)
  const options = [
    { label: 'Option 1', value: 'option1' },
    { label: 'Option 2', value: 'option2' },
    { label: 'Option 3', value: 'option3' },
  ]
  const handleChange = value => {
    selectedValue.value = value
  }
</script>

Custom Rendering

The select component supports powerful custom rendering capabilities. This enables you to customize how options appear in the dropdown menu and how selected values are displayed as tags. We recommend using the option and tag slots—they are the preferred way to pass custom option and tag content. The optionRenderer prop and option-level tagRenderer remain supported for backward compatibility but are planned for deprecation.

  • option slot: Customizes how each option is rendered in the dropdown menu.
  • tag slot: Customizes the content rendered inside each selected tag in multiple selection mode, and the selected value in single selection mode.

Note

The slot content does not replace the tag itself. The tag component remains the wrapper, and option.tagProps still applies to that wrapper. Use the slot only for the inner content.


Alpha
vue
<template>
  <HLSelect
    id="select-slot-renderers"
    multiple
    filterable
    showSearchIcon
    :options="slotRenderOptions"
    :value="slotSelectedValue"
    :maxTagCount="'responsive'"
    aria-label="Select using slot renderers"
    @update:value="handleSlotRenderChange"
  >
    <template #option="{ option, selected, disabled }">
      <div class="flex items-center justify-between w-full gap-2">
        <div class="flex flex-col">
          <HLText size="sm" :class="disabled ? 'text-gray-400' : 'text-gray-900'">
            {{ option.label }}
          </HLText>
          <HLText v-if="option.description" size="xs" class="text-gray-500">
            {{ option.description }}
          </HLText>
        </div>
        <HLTag v-if="selected" size="xs" round color="success">Selected</HLTag>
      </div>
    </template>
    <template #tag="{ option, disabled }">
      <span class="flex items-center gap-1">
        <HLIcon v-if="!disabled" size="xs"><CheckVerified01Icon /></HLIcon>
        <span>{{ option.label }}</span>
      </span>
    </template>
  </HLSelect>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { HLSelect, HLTag, HLText, HLIcon } from '@platform-ui/highrise'
import { CheckVerified01Icon } from '@gohighlevel/ghl-icons/24/outline'
import { slotRenderOptions } from './options'

const slotSelectedValue = ref(['alpha'])

const handleSlotRenderChange = value => {
  slotSelectedValue.value = value
}
</script>
ts
export const slotRenderOptions = [
  {
    label: 'Alpha',
    value: 'alpha',
    description: 'Primary choice',
  },
  {
    label: 'Beta',
    value: 'beta',
    description: 'Secondary option',
    disabled: true,
  },
  {
    label: 'Gamma',
    value: 'gamma',
    description: 'Experimental option',
  },
]

Render function API (deprecated)

Deprecation

The optionRenderer prop and option-level tagRenderer are still supported but will be deprecated in a future release. Prefer the option and tag slots above for custom option and tag rendering.

Custom rendering API:

  • optionRenderer - Customizes how each option is rendered in the dropdown menu via a render function.
  • tagRenderer - Customizes how the selected value is rendered in the trigger (single select) or inside the tag (multiple select) via a string, VNode, or render function. The render function should return a valid VNodeChild; if it returns undefined or null, the option label is used as fallback.
Please Select
vue
<script setup lang="ts">
  import { h, ref } from 'vue'
  import { HLSelect, HLSpace, HLIcon, HLAvatar, HLTag, HLText } from '@platform-ui/highrise'
  import { CheckVerified01Icon } from '@gohighlevel/ghl-icons/24/outline'

  const selectedValue = ref(null)
  const handleChange = value => {
    selectedValue.value = value
  }

  const customOptions = [
    {
      type: 'group',
      label: 'Custom Rendering Examples',
      key: 'custom-group',
      children: [
        {
          label: 'Custom Label with Tag',
          value: 'option1',
          tagRenderer: 'Custom Tag',
          backgroundColor: 'var(--green-50)',
          color: 'var(--green-700)',
          renderOption: () =>
            h('div', { class: 'flex items-center gap-2' }, [
              h('div', 'Custom Label'),
              h(HLTag, { size: 'sm', round: true, variant: 'error' }, { default: () => '2% increase' }),
            ]),
        },
        {
          label: 'Verified Option',
          value: 'option2',
          description: 'With icon and description',
          backgroundColor: 'var(--purple-50)',
          color: 'var(--purple-700)',
          renderOption: () =>
            h(
              HLSpace,
              { align: 'center', wrapItem: false },
              {
                default: () => [h(HLIcon, { size: 'sm' }, { default: () => h(CheckVerified01Icon) }), h('span', 'Verified Option')],
              }
            ),
        },
        {
          label: 'USA',
          value: 'option3',
          backgroundColor: 'var(--orange-50)',
          color: 'var(--orange-700)',
          renderOption: () =>
            h(
              HLSpace,
              { align: 'center', wrapItem: false, justify: 'space-between' },
              {
                default: () => [
                  h(
                    HLSpace,
                    { align: 'center', wrapItem: false },
                    {
                      default: () => [
                        h(HLAvatar, {
                          size: 'xs',
                          src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=USA',
                        }),
                        h('span', 'USA'),
                      ],
                    }
                  ),
                  h(HLText, { size: 'xs' }, { default: () => '+1' }),
                ],
              }
            ),
          tagRenderer: () =>
            h(
              HLSpace,
              { align: 'center', wrapItem: false },
              {
                default: () => [
                  h('img', {
                    style: { width: '16px', height: '16px', borderRadius: '50%' },
                    src: 'https://api.dicebear.com/9.x/avataaars/svg?seed=USA',
                  }),
                  h('span', 'USA'),
                ],
              }
            ),
        },
      ],
    },
  ]

  const optionRenderer = option => {
    if (option.renderOption) {
      return option.renderOption()
    }

    return h('span', option.label)
  }
</script>

<template>
  <HLSelect
    :options="customOptions"
    :value="selectedValue"
    @update:value="handleChange"
    :option-renderer="optionRenderer"
    multiple
    filterable
    id="select-custom-render"
  />
</template>

Trigger and Menu Customization

Fine-tune the trigger and dropdown appearance: triggerFontSize / triggerFontWeight style the selected text, showArrow toggles the dropdown chevron, showCheckmark toggles the selected-option tick, optionHeight sets the row height, menuProps passes attributes to the dropdown menu, and wrapperClass adds a class to the wrapper.

Vue
vue
<template>
  <HLSelect
    id="select-trigger-custom"
    :options="options"
    :value="value"
    @update:value="val => (value = val)"
    trigger-font-size="lg"
    trigger-font-weight="semibold"
    :show-arrow="true"
    :show-checkmark="false"
    :option-height="48"
    :menu-props="{ 'data-testid': 'custom-menu' }"
    wrapper-class="custom-select-wrapper"
  />
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import { HLSelect } from '@platform-ui/highrise'

  const value = ref('option2')
  const options = [
    { label: 'Option 1', value: 'option1' },
    { label: 'Option 2', value: 'option2' },
    { label: 'Option 3', value: 'option3' },
  ]
</script>

Custom Value Field

By default each option's value is read from its value key. Use valueField to read it from a different key — e.g. id — so @update:value returns that field.

Select one
Vue
vue
<template>
  <HLSelect
    id="select-value-field"
    :options="options"
    :value="value"
    value-field="id"
    placeholder="Select one"
    @update:value="val => (value = val)"
  />
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import { HLSelect } from '@platform-ui/highrise'

  const value = ref(null)
  // Options use `id` instead of `value` as the value key
  const options = [
    { label: 'Alpha', id: 'a' },
    { label: 'Beta', id: 'b' },
    { label: 'Gamma', id: 'c' },
  ]
</script>

Event Testing

This example logs the events the select emits as you interact with it. Try the following:

  • Open and close the dropdown to test @update:show
  • Select or clear a value to test @update:value and @clear
  • Type in the search box (filterable) to test @search
  • Focus and blur the trigger to test @focus and @blur

Event Log:

No events logged yet. Interact with the select above.
Vue
vue
<template>
  <HLSelect
    id="select-events"
    :options="options"
    :value="value"
    clearable
    filterable
    @update:value="handleChange"
    @update:show="val => addEventLog('@update:show → ' + val)"
    @search="val => addEventLog('@search → ' + val)"
    @clear="addEventLog('@clear')"
    @focus="addEventLog('@focus')"
    @blur="addEventLog('@blur')"
  />
  <div class="text-sm">
    <p class="font-bold mb-2">Event Log:</p>
    <div v-if="eventLog.length === 0" class="text-gray-500">No events logged yet. Interact with the select above.</div>
    <div v-for="(log, index) in eventLog" :key="index" class="text-gray-700">{{ log.timestamp }}: {{ log.event }}</div>
  </div>
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import { HLSelect } from '@platform-ui/highrise'

  const value = ref(null)
  const options = [
    { label: 'Option 1', value: 'option1' },
    { label: 'Option 2', value: 'option2' },
    { label: 'Option 3', value: 'option3' },
  ]

  const eventLog = ref<{ event: string; timestamp: string }[]>([])
  const addEventLog = (event: string) => {
    eventLog.value.unshift({ event, timestamp: new Date().toLocaleTimeString() })
    if (eventLog.value.length > 5) {
      eventLog.value.pop()
    }
  }
  const handleChange = (val: string | number | null) => {
    value.value = val
    addEventLog('@update:value → ' + JSON.stringify(val))
  }
</script>

Design Guidelines

Input components use a box-shadow to render their focus ring. Box-shadows render outside the element's bounds and may be clipped by any ancestor using overflow: hidden (e.g. Tab Panels or Dropdown Menus).

To prevent this, add a small gutter padding to the component's wrapper to ensure there is enough room for the focus ring to render without being cut off.

vue
<div class="p-[3px]">
  <!-- Your component here -->
</div>

Accessibility

  • Tie helper/error copy via aria-describedby, and expose loading states with aria-busy when fetching options.
  • The trigger exposes combobox/listbox semantics (aria-expanded, aria-controls, aria-haspopup) automatically; pass ariaLabel or ariaLabelledby so screen readers announce intent consistently.

Imports


ts
import { HLSelect } from '@platform-ui/highrise'

Props

PropTypeDefaultDescription
idstringAuto (hr-select-*)Unique identifier for the select. When omitted the component generates an accessible id used for the trigger and dropdown.
allowTagTruncationbooleanfalseEnable truncation of tag text in multiple select mode with ellipsis
ariaLabelstring | undefinedundefinedARIA label for accessibility. Provides accessible name for screen readers
ariaLabelledbystring | undefinedundefinedID of element that labels this select. Use when label is provided by another element
clearableboolean | undefinedundefinedShows a clear button to reset the selection; emits @clear when used
disabledboolean | undefinedundefinedDisables the select
filterableboolean | undefinedundefinedEnables search/filter functionality
loadingboolean | undefinedundefinedShows loading state
maxTagCountnumber | 'responsive''responsive'Maximum number of visible tags
maxTagWidthstring | number136Maximum width for truncated tags (in pixels if number, or CSS units if string)
menuPropsHTMLAttributes{}Additional props to pass to the dropdown menu
multiplebooleanfalseEnables multiple selection
optionHeightnumber | undefinedundefinedHeight of each option in pixels
optionRenderer(option: SelectOption) => VNodeChild | undefinedundefinedCustom function to render options
optionsSelectOption[][]Array of options to display
placeholderstring | undefinedundefinedPlaceholder text when no selection
remotebooleanfalseEnable remote search mode. When true, the component will emit search events instead of filtering options locally. Required for async data fetching scenarios.
resetMenuOnOptionsChangebooleanfalseControls whether the dropdown menu position resets when options array changes. Set to true for dynamic content scenarios where menu positioning needs to update.
roundedTagsbooleanfalseUse rounded styling for tags
showboolean | undefinedundefinedControls dropdown visibility
showArrowboolean | undefinedundefinedShow/hide dropdown arrow
showAvatarInTagsbooleantrueShow avatar in multiple selection tags
showCheckmarkboolean | undefinedundefinedShow/hide option checkmark
showSearchIconbooleanfalseShows search icon in input
size'lg' | 'md' | 'sm' | 'xs' | '2xs' | '3xs'mdSize of the select
tostring | boolean | HTMLElement | undefinedundefinedTeleport dropdown to element/disable
triggerFontSizeHLTextSizeundefinedFont size for the trigger text
triggerFontWeightHLTextWeightundefinedFont weight for the trigger text
type'default' | 'avatar''default'Visual style variant
valuestring | string[] | nullundefinedSelected value(s)
valueFieldstring'value'Field name to use as the value in options
wrapperClassstringundefinedAdditional CSS class for the wrapper element

Types

SelectOption

The options prop accepts an array of SelectOption objects.

ts
interface SelectOption {
  description?: string // Optional description text for the option
  disabled?: boolean // If true: option is not selectable in the dropdown, and when selected its tag is disabled (visual + non-closable)
  label?: string // Option label text
  /** @deprecated Use tagProps.color instead */
  tagColor?: HLTagColor // Color of the tag (when used in tags mode)
  src?: string // Source URL (e.g. for avatar images)
  tagRenderer?: (() => VNodeChild) | VNodeChild
  type: HLSelectOptionType // Type of the option ('group' | 'divider')
  value?: string | number // The value of the option
  /** Complete tag props object for full control over tag rendering */
  tagProps?: SelectTagProps // Advanced tag configuration
}

interface SelectTagProps {
  color?: HLTagColor // Tag color
  bordered?: boolean // Whether tag has border
  closable?: boolean // Whether tag can be closed
  disabled?: boolean // Whether tag is disabled
  round?: boolean // Whether tag is rounded
  count?: number // Count to display in tag
  dropdown?: 'open' | 'close' | false // Dropdown state
  interactive?: boolean // Whether tag is interactive
  truncate?: boolean // Whether to truncate text
  maxWidth?: string | number // Maximum width for truncated text
  avatarProps?: any // Avatar props (HLAvatarProps)
}

Group Option

When type is 'group', the option can include children:

ts
interface SelectGroupOption extends SelectOption {
  type: 'group'
  label: string // Group header text
  key: string // Unique identifier for the group
  children: SelectOption[] // Array of child options
}

Divider Option

When type is 'divider', it renders a horizontal line separator:

ts
interface SelectDividerOption {
  type: 'divider'
}

Examples

js
// Basic option
{
  label: "Option 1",
  value: "opt1",
  description: "Optional description"
}
js
// Group with children
{
  type: "group",
  label: "Group 1",
  key: "group1",
  children: [
    { label: "Child 1", value: "child1" },
    { type: "divider" },
    { label: "Child 2", value: "child2" }
  ]
}
js
// Tag Renderer
{
  label: "Option 1",
  value: "opt1",
  tagRenderer: () => h(HLTag, { color: "blue" }, "Tag Content")
}

Emits

NameParametersDescription
@update:value(value: string | number | string[] | number[] | null, option: SelectOption | SelectOption[] | null)Triggered when selection changes
@update:show(value: boolean)Triggered when dropdown visibility changes
@scroll(e: Event)Triggered on dropdown scroll
@search(value: string)Triggered when search input changes
@clear()Triggered when selection is cleared
@focus()Triggered when input is focused
@blur()Triggered when input is blurred

Methods

NameParametersDescription
focusInput()Focuses the input field
blurInput()Blurs the input field
focus()Focuses the select
blur()Blurs the select

Slots

NameParametersDescription
default()The default content slot
icon()Custom icon slot
header()Header content for dropdown
action()Action content for dropdown
arrow()Custom arrow icon
empty()Content when no options exist
option{ option, selected, disabled, multiple, value }Custom option renderer slot
tag{ option, selected, disabled, multiple, value, handleClose }Customizes the content rendered inside each selected tag for multiple selection and the selected value for single selection
edit-actions()Edit actions content for inline mode