Migrating from ghl-ui AdvancedSelect to HighRise AdvancedSelect
UIAdvancedSelect maps directly to HLAdvancedSelect. The API is broadly similar — searchable dropdown with single/multiple/tag modes, custom triggers, remote search, and tag creation — but the option model, prop defaults, and event names have changed. The biggest schema shift: HighRise options use key and src (inherited from HLDropdownOption) instead of value and imageUrl.
Component Implementation Changes
Import Changes
diff
- import { UIAdvancedSelect } from '@gohighlevel/ghl-ui'
+ import { HLAdvancedSelect } from '@platform-ui/highrise'Basic Usage Changes
diff
- <UIAdvancedSelect
- :value="selectedValue"
- :options="options"
- trigger-placeholder="Select a person"
- @onChange="v => selectedValue = v"
- />
+ <HLAdvancedSelect
+ :value="selectedValue"
+ :options="options"
+ trigger-placeholder="Select a person"
+ @update:value="v => selectedValue = v"
+ />Option Model Changes
diff
- const options = [
- { label: 'John Doe', value: '1', imageUrl: 'https://example.com/john.jpg' },
- { label: 'Jane Smith', value: '2' },
- ]
+ const options = [
+ { label: 'John Doe', key: '1', type: 'avatar', src: 'https://example.com/john.jpg' },
+ { label: 'Jane Smith', key: '2', type: 'avatar' },
+ ]HLAdvancedSelectOption extends HLDropdownOption:
value→key(unique identifier)imageUrl→src- Add
type: 'avatar'for avatar rendering with initials fallback; omit for text-only rows. children(for grouped options) andmeta(arbitrary per-option data) still supported.
Props Changes
| ghl-ui Prop | HighRise Prop | Notes |
|---|---|---|
value | value (+ @update:value) | Same shape (string | number | (string | number)[] | null); event renamed from onChange. |
options | options | Same array shape, but option fields changed — see above. |
multiple | multiple | Same. |
selectType | selectType | Same: 'default' | 'tags'. |
remote | remote | Same; pair with @search. |
loading | loading | Same. |
showSearch | showSearch | Same. |
searchPlaceholder | searchPlaceholder | Same. |
triggerPlaceholder | triggerPlaceholder | Same. |
maxTagCount | maxTagCount | Same — accepts a number to cap visible tags. |
virtualScroll | virtualScroll | Same — pair with popoverWidth for stable dropdown sizing. |
placement | placement | Same. Default is 'bottom-start'. |
show | show (+ @update:show) | Controlled visibility; event renamed from onClose. |
to | to | Same teleport target. |
showAddTagCTA | showAddTagCTA | Same; only active in selectType="tags". |
handleNewOptionRemote | handleNewOptionRemote | Same; emits @newTag instead of onNewTag. |
disabled | disabled | Same. |
resetSearchString | resetSearchString | Same. |
popoverWidth | popoverWidth | Same: number | 'trigger'. |
popoverContainerClass | popoverContainerClass | Same. |
renderOption | #option-renderer slot | Prop replaced by slot — see Custom Option Rendering below. |
randomColor | — | Removed. Trigger avatars use HLAvatar's default name-based coloring; no toggle. |
sortSelectedValues | dockSelectedToTop | Renamed; default flipped to false. Set :dock-selected-to-top="true" to restore the V1 default. |
trigger ('click' | 'hover' | 'focus' | 'manual') | — | Removed. Trigger interaction is always click-based. |
filterTags | — | Removed. Built-in case-insensitive substring filter; use remote + @search for custom logic. |
| — | maxAvatarCount | New: cap avatars in the trigger before collapsing to +N (default 5). |
| — | maxHeight | New: dropdown options area max height (default '320px'). |
| — | defaultTagProps | New: Partial<HLTagProps> applied to tags in the InputTag trigger (e.g. { round: true }). |
| — | defaultAvatarProps | New: Partial<HLAvatarProps> applied to the trigger's AvatarGroup. |
| — | showArrow | New: optional popover arrow (default false). |
Emits Changes
All events were renamed from onX (camelCase) to Vue's update:X / kebab-case convention.
| ghl-ui Event | HighRise Event | Notes |
|---|---|---|
@onChange | @update:value | Same payload. Enables v-model:value. |
@onSearch | @search | Same payload. |
@onScroll | @scroll | Same payload { clientHeight, scrollHeight, scrollTop }. |
@onClose | @close (or @update:show with false) | Both available; prefer @update:show for controlled visibility. |
@onNewTag | @newTag | Same payload (the typed label). |
@onChangeSrc | — | Removed. V1 emitted 'DROPDOWN' or 'TRIGGER' to indicate which UI caused a selection change; V2 does not distinguish — wrap @update:value with your own source tracking if you need it. |
Slots Changes
| ghl-ui Slot | HighRise Slot | Notes |
|---|---|---|
#trigger | #trigger | Same; receives { disabled, value, placeholder }. |
#empty | #empty | Same. |
#emptySearch | #emptySearch | Same; receives { searchString }. |
#action-top | #header | Replaced. Position differs: V1's #action-top sat next to the search input; V2's #header renders above the search input as sticky content. |
#action-bottom | #footer | Replaced. Renders below the options list. |
renderOption (prop) | #option-renderer | Prop → slot; receives { option } where option.selected reflects current selection. The slot fully replaces the default row, including its click handler — wire @click yourself. |
| — | #loader | New: content at the bottom of the options list (use for infinite scroll). |
Examples
Multiple Select with Remote Search
vue
<template>
<HLAdvancedSelect
:options="remoteOptions"
:value="selected"
:loading="loading"
multiple
remote
trigger-placeholder="Search people"
@update:value="v => selected = v"
@search="onSearch"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { HLAdvancedSelect } from '@platform-ui/highrise'
const selected = ref<(string | number)[]>([])
const loading = ref(false)
const remoteOptions = ref([
{ label: 'Alice', key: 'alice', type: 'avatar' },
{ label: 'Bob', key: 'bob', type: 'avatar' },
])
const onSearch = async (query: string) => {
loading.value = true
// fetch and set remoteOptions.value
loading.value = false
}
</script>Tags Mode with Create
vue
<template>
<HLAdvancedSelect
:options="tagOptions"
:value="tagValue"
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"
:default-tag-props="{ round: true }"
@update:value="v => tagValue = v"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { HLAdvancedSelect } from '@platform-ui/highrise'
const tagValue = ref<string[]>([])
const tagOptions = ref([
{ label: 'Bug', key: 'bug' },
{ label: 'Feature', key: 'feature' },
])
</script>Custom Option Rendering
renderOption (a prop returning VNodes) is replaced by the #option-renderer slot. The slot receives { option } with option.selected already resolved, and replaces the default row entirely — including click handling.
diff
- <UIAdvancedSelect
- :options="options"
- :value="value"
- :render-option="renderOption"
- @onChange="v => value = v"
- />
- <script setup>
- const renderOption = (option, isActive) => h('div', {
- class: isActive ? 'bg-primary-50' : ''
- }, option.label)
- </script>
+ <HLAdvancedSelect
+ :options="options"
+ :value="value"
+ @update:value="v => value = v"
+ >
+ <template #option-renderer="{ option }">
+ <div
+ :class="option.selected ? 'bg-primary-50' : ''"
+ @click="toggle(option.key)"
+ >
+ {{ option.label }}
+ </div>
+ </template>
+ </HLAdvancedSelect>Breaking Changes
- Option identifier —
value→key. Update both option arrays and any code that reads from options (e.g..find(o => o.value === ...)→.find(o => o.key === ...)). - Avatar image field —
imageUrl→src; addtype: 'avatar'to opt into avatar rendering. - Event names —
onChange/onSearch/onScroll/onClose/onNewTag→update:value/search/scroll/close/newTag.onChangeSrcis gone. renderOptionprop →#option-rendererslot — slot fully replaces the default row, including its click handler.sortSelectedValues→dockSelectedToTop— renamed and default flipped tofalse.randomColor,trigger,filterTagsremoved — no replacement; rely on built-in defaults or useremotefor custom search.- Action slot rename —
#action-top→#header,#action-bottom→#footer.
Best Practices
- Always use
key(notvalue) as the unique identifier on options. - Set
type: 'avatar'on options that need image/initials rendering. - Use
remote+@searchfor server-side filtering; updateoptionswith API results. - Enable
virtual-scrollfor lists over ~50 options and always pair with a fixedpopoverWidthto keep the dropdown size stable. - Use
#header/#footerslots for sticky content (counts, action buttons). - Prefer
v-model:valueandv-model:showover manual event wiring.
TypeScript Support
ts
import type {
HLAdvancedSelectProps,
HLAdvancedSelectOption,
HLAdvancedSelectValue,
} from '@platform-ui/highrise'
interface HLAdvancedSelectOption extends HLDropdownOption {
children?: HLAdvancedSelectOption[]
meta?: any
}