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

Select

Select component for choosing single or multiple options

Default Select


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

  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


Please Select
html
<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>
  import { HLSelect } from '@platform-ui/highrise'
  import { User01Icon } from '@gohighlevel/ghl-icons/24/outline'
  import { ref } from 'vue'

  const selectedValue = ref('')
  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
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


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

  const selectedValue = ref('')
  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
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
    ],
  },
]

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
html
<template>
  <HLSelect :options="options" v-model:value="selectedValue2" />
  <HLButton @click="selectedValue2 = null">Clear Value</HLButton>
</template>
<script setup>
  import { HLSelect } 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


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

  const selectedValue = ref('')
  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
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


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

  const selectedValue = ref('')
  const handleSimpleMultiChange = value => {
    selectedValue.value = value
  }
</script>
ts
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
html
<template>
  <HLSelect
    multiple
    filterable
    showSearchIcon
    :allowTagTruncation="true"
    :maxTagWidth="120"
    :options="options"
    :value="selectedValue"
    @update:value="handleChange"
    id="select-truncated"
    :showAvatarInTags="false"
  />
</template>
<script setup>
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'

  const selectedValue = ref('')
  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
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.


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


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

  const infiniteSelectedValue = ref(null)
  const infiniteLoading = ref(false)
  const infiniteSelectedValue = ref(null)
  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>

Inline Select Variants

Default Inline


Please Select
html
<template>
  <HLSelect :options="options" :value="selectedValue" @update:value="handleChange" :inline="true" size="sm" />
</template>
<script setup>
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'

  const selectedValue = ref('')
  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
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
    ],
  },
]

Inline with CTA


Please Select
html
<HLSelect
  :options="options"
  :value="selectedValue"
  @update:value="handleChange"
  :inline="true"
  :showInlineCTA="showInlineCTA"
  :show="show"
  :filterable="true"
  size="sm"
  @handleConfirm="handleConfirm"
  @handleCancel="handleCancel"
  @update:show="handleUpdateShow"
/>
ts
const show = ref(false)
const showInlineCTA = ref(false)

const handleConfirm = () => {
  show.value = false
  showInlineCTA.value = false // Hide CTA only on confirm
}

const handleCancel = () => {
  selectedValue.value = null
  show.value = false
  showInlineCTA.value = false // Hide CTA only on cancel
}
const handleUpdateShow = value => {
  show.value = value
  showInlineCTA.value = true
}

Inline with Bottom Border


Please Select
html
<template>
  <HLSelect
    :options="options"
    :value="selectedValue"
    @update:value="handleChange"
    :inline="true"
    :showInlineBottomBorder="true"
    size="sm"
  />
</template>
<script setup>
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'

  const selectedValue = ref('')
  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
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
    ],
  },
]

Inline with edit actions


Please Select
html
<HLSelect
  :options="options"
  :value="selectedValue"
  @update:value="handleChange"
  :inline="true"
  :showInlineCTA="showInlineCTA"
  :showSavedMark="showSavedMark"
  @handleConfirm="handleConfirm"
  @handleCancel="handleCancel"
  @update:show="handleUpdateShow"
  size="sm"
  id="select-inline-edit-actions"
>
  <template #edit-actions>
    <HLIcon size="16" color="var(--success-600)"><PhoneIcon /></HLIcon>
    <HLIcon size="16"><Mail01Icon /></HLIcon>
  </template>
</HLSelect>
ts
import { HLSelect } from '@platform-ui/highrise'
import { ref } from 'vue'

const showInlineCTA = ref(false)
const showSavedMark = ref(false)
const selectedValue = ref('')
const handleChange = value => {
  selectedValue.value = value
}
const handleConfirm = () => {
  showInlineCTA.value = false
  showSavedMark.value = true
  setTimeout(() => {
    showSavedMark.value = false
  }, 2500)
}
const handleCancel = () => {
  selectedValue.value = null
  showInlineCTA.value = false
  showSavedMark.value = false
}
const handleUpdateShow = value => {
  showInlineCTA.value = value
  if (value) {
    showInlineCTAEditActions.value = true
  }
}
ts
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...',
        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
    ],
  },
]

Rounded Tags


Please Select
html
<template>
  <HLSelect
    :options="options"
    :value="selectedValue"
    @update:value="handleChange"
    :inline="true"
    :multiple="true"
    type="avatar"
    :filterable="true"
    :showSearchIcon="true"
    :roundedTags="true"
    size="sm"
  />
</template>
<script setup>
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'

  const selectedValue = ref('')
  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
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
    ],
  },
]

Disabled Inline Select


Please Select
Vue
html
<template>
  <HLSelect :options="options" :value="selectedValue" @update:value="handleChange" :inline="true" :disabled="true" size="sm" />
</template>
<script setup>
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'

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

Error State


Please Select
html
<template>
  <HLFormItem validation-status="error" feedback="This field is required">
    <HLSelect
      :options="options"
      :value="selectedValue"
      @update:value="handleChange"
      :inline="true"
      :showInlineCTA="showInlineCTA"
      :show="show"
      :filterable="true"
      size="sm"
      @handleConfirm="handleConfirm"
      @handleCancel="handleCancel"
      @update:show="handleUpdateShow"
    />
  </HLFormItem>
</template>
<script setup>
  import { HLSelect } from '@platform-ui/highrise'
  import { ref } from 'vue'

  const selectedValue = ref('')
  const handleChange = value => {
    selectedValue.value = value
  }
</script>
ts
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
    ],
  },
]

Advanced Tag Configuration with tagProps

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

Basic Usage

html
<template>
  <HLSelect multiple :options="options" :value="selectedValue" @update:value="handleChange" />
</template>

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

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

  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: 'Tag with Custom Avatar',
      value: 'option3',
      tagProps: {
        color: 'blue',
        avatarProps: {
          size: 'xs',
          objectFit: 'cover',
          round: true,
          src: 'https://example.com/avatar.jpg',
          indicator: true,
          name: 'Custom Avatar',
        },
      },
    },
    {
      label: 'Disabled Tag',
      value: 'option4',
      tagProps: {
        color: 'gray',
        disabled: true,
        closable: false,
      },
    },
  ]

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

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
checkboxbooleanShow checkboxfalse
checkedbooleanChecked statefalse
avatarPropsHLAvatarPropsAvatar configurationundefined

Avatar Configuration

The avatarProps property allows you to customize avatar appearance in tags:

js
{
  label: 'User with Custom Avatar',
  value: 'user1',
  tagProps: {
    color: 'primary',
    avatarProps: {
      size: 'xs',                    // Avatar size
      objectFit: 'cover',            // How image fits in avatar
      round: true,                  // Rounded avatar
      src: 'https://example.com/user.jpg', // Avatar image URL
      indicator: true,              // Show online indicator
      name: 'John Doe',             // Custom name (overrides 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
html
<div id="select-teleport-target"></div>
<HLSelect :options="options" :value="selectedValue" @update:value="handleChange" to="#select-teleport-target" id="select-teleport" />

Custom Rendering

The select component provides powerful customization options through renderOption and tagRenderer props. These allow you to customize both how options appear in the dropdown menu and how selected values are displayed as tags.

How Custom Rendering Works

  1. Render Option (renderOption):

    • Customizes how the option appear in the dropdown menu
    • Can return plain text or complex Vue components
    • Has access to the full option object including custom properties
  2. Tag Rendering (tagRenderer):

    • Single Selection: Completely customizes how the selected value appears in the input
    • Multiple Selection: Customizes the content inside the tag wrapper, but the tag wrapper itself remains unchanged
    • The tag wrapper (HLTag component) is always present in multiple selection mode
    • Only the content within the tag can be customized via tagRenderer
  3. Option Properties:

    • label: The display text or component
    • value: The underlying value
    • tagRenderer: Optional custom tag rendering
    • renderOption: Optional custom label rendering
    • description: Additional text shown below the label
    • Custom styling properties (backgroundColor, color, etc.)
  4. Option Renderer:

    • Customizes how a option appear in the dropdown menu
    • Can return plain text or complex Vue components
    • It will have parameter option

Please Select
html
<script setup>
  import { h } from 'vue'
  import { HLSpace, HLIcon, HLAvatar, HLTag, HLText } from '@platform-ui/highrise'
  import { CheckVerified01Icon } from '@gohighlevel/ghl-icons/24/outline'

  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://upload.wikimedia.org/wikipedia/en/thumb/a/a4/Flag_of_the_United_States.svg/1920px-Flag_of_the_United_States.svg.png',
                        }),
                        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://upload.wikimedia.org/wikipedia/en/thumb/a/a4/Flag_of_the_United_States.svg/1920px-Flag_of_the_United_States.svg.png',
                  }),
                  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>

Imports


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

Props

PropTypeDefaultDescription
id *stringundefinedUnique identifier for the select
allowTagTruncationbooleanfalseEnable truncation of tag text in multiple select mode with ellipsis
disabledboolean | undefinedundefinedDisables the select
filterableboolean | undefinedundefinedEnables search/filter functionality
inlinebooleanfalseUse inline styling
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
showInlineBottomBorderbooleanfalseShow bottom border in inline mode
showInlineCTAbooleanfalseShow confirm/cancel buttons in inline mode
showSavedMarkbooleanfalseShow saved mark in inline mode
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
  label?: string | (() => VNodeChild) // Option label text or render function
  /** @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 // Custom tag render function (content inside tag only)
  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 {
  size?: 'lg' | 'md' | 'sm' | 'xs' // Tag size
  color?: HLTagColor // Tag color
  bordered?: boolean // Whether tag has border
  checkbox?: boolean // Whether tag has checkbox
  checked?: boolean // Whether tag is checked (if checkbox)
  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, option: SelectOption)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
edit-actions()Edit actions content for inline mode