Combobox
The Combobox consists of a label, a text-input field, and a chevron icon that indicates the control can expand. The input field contains the current value or placeholder text and is enclosed by a container that provides focus and hover states. Activating the control reveals the dropdown menu, which presents a list of options.
import {ComboboxModule} from "@qualcomm-ui/angular/combobox"Overview
- Each combobox uses the
comboboxCollectionhelper to manage the list of options. This creates aListCollectioninstance, documented below.
Examples
Simple
The simple API provides a standalone component with built-in layout.
<div class="flex flex-col gap-4">
<q-combobox
class="w-56"
label="Country"
placeholder="Select a country"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
</div>
Composite
Build with the composite API for granular control. This API requires you to provide each subcomponent, but gives you full control over the structure and layout.
<div
class="w-56"
placeholder="Select a country"
q-combobox-root
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
>
<label q-combobox-label>Search</label>
<div q-combobox-control>
<input q-combobox-input />
<button q-combobox-clear-trigger></button>
<div q-combobox-trigger></div>
</div>
<ng-template qPortal>
<div q-combobox-positioner>
<div q-combobox-content>
<div q-combobox-empty>No results found</div>
<q-combobox-items />
</div>
</div>
</ng-template>
</div>
Open on Click
Use the openOnClick prop to open the combobox when the user clicks on the input.
<q-combobox
class="w-56"
label="Country"
openOnClick
placeholder="Select a country"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
Input Behavior
Adjust the auto-completion behavior of the combobox with the inputBehavior prop.
<q-combobox
class="w-56"
inputBehavior="autohighlight"
label="Country"
placeholder="Select a country"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
Multiple Selection
Use the multiple prop to enable multiple selection. When this is set, the combobox will always clear the input value when the user selects an option.
import {Component, signal} from "@angular/core"
import {FormsModule} from "@angular/forms"
import {useListCollection} from "@qualcomm-ui/angular-core/collection"
import {ComboboxModule} from "@qualcomm-ui/angular/combobox"
import {TagDirective} from "@qualcomm-ui/angular/tag"
import type {ComboboxInputValueChangeDetails} from "@qualcomm-ui/core/combobox"
import {countries} from "./country-list"
@Component({
imports: [ComboboxModule, TagDirective, FormsModule],
selector: "combobox-multiple-demo",
template: `
<div class="flex w-72 flex-col gap-4">
<div class="flex flex-wrap gap-2">
@for (item of value(); track item) {
<button
emphasis="neutral"
q-tag
variant="dismissable"
(click)="handleValueDismissed(item)"
>
{{ item }}
</button>
}
</div>
<q-combobox
class="w-full"
label="Country"
multiple
placeholder="Select a country"
[collection]="listCollection.collection()"
[(ngModel)]="value"
(inputValueChanged)="onInputChange($event)"
/>
</div>
`,
})
export class ComboboxMultipleDemo {
readonly value = signal<string[]>([])
readonly listCollection = useListCollection<string>({
filter: "contains",
items: countries,
})
onInputChange(event: ComboboxInputValueChangeDetails) {
this.listCollection.filterText.set(event.inputValue)
}
handleValueDismissed(item: string) {
this.value.set(this.value().filter((v) => v !== item))
}
}Highlight Matching Text
Use the highlightMatchingText prop to highlight the portion of each option that matches the user's input. This provides visual feedback during filtering.
<q-combobox
class="w-56"
highlightMatchingText
label="Country"
name="combobox-highlight"
placeholder="Search countries..."
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
ARIA Label
The Combobox's label is automatically associated with the input element for accessibility. If you omit the label, give the input an accessible name with aria-label or aria-labelledby.
<q-combobox
aria-label="Country"
class="w-48"
placeholder="Select a country"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
NOTE
Use aria-label or aria-labelledby on the simple <q-combobox> to forward an accessible name to the internal input. The q-combobox host removes those attributes after forwarding them. If you use the composite API, provide aria-label or aria-labelledby on the q-combobox-input element. For dynamic label references, use [aria-labelledby] instead of [attr.aria-labelledby] so the input can treat the value as a consumer-provided override.
Content Width
The positioning.sameWidth property controls whether the width of the combobox content matches the width of the input element. The default is true.
<q-combobox
aria-label="Country"
class="w-48"
placeholder="Select a country"
[collection]="listCollection.collection()"
[positioning]="{sameWidth: false}"
(inputValueChanged)="onInputChange($event)"
/>
Input Icon
Use the icon prop to add an icon to the start of the input element. View our Icon documentation to learn more about using icons in QUI.
<q-combobox
aria-label="Country"
class="w-48"
icon="MapPin"
placeholder="Select a country"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
Icon Customization
Provide a custom element using the q-combobox-icon directive to override the default icon. This example features a loading indicator:
<q-combobox
aria-label="Country"
class="w-48"
placeholder="Select a country"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
>
@if (loading()) {
<div q-combobox-icon q-progress-ring size="xs"></div>
}
</q-combobox>
Items as Objects
The items prop can be an array of objects. Use the itemLabel and itemValue properties on the comboboxCollection to specify the label and value of each item.
<q-combobox
class="w-48"
label="City"
placeholder="Select a city"
[collection]="cityCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
Item Customization
When using the simple API, provide the q-combobox-content directive as a child of the <q-combobox> component to override the default item renderer. The following example demonstrates how to add a custom icon to each item:
<q-combobox
class="w-56"
label="Country"
placeholder="Select a country"
[collection]="listCollection.collection()"
[icon]="valueIcon()"
[(ngModel)]="value"
(inputValueChanged)="onInputChange($event)"
>
<div q-combobox-content>
<div q-combobox-empty>No results found</div>
@for (
item of listCollection.collection().items;
track listCollection.collection().getItemValue(item)
) {
<div q-combobox-item [item]="item">
<svg [qIcon]="item.icon"></svg>
<span q-combobox-item-text>
{{ listCollection.collection().stringifyItem(item) }}
</span>
<span q-combobox-item-indicator></span>
</div>
}
</div>
</q-combobox>
TIP
When you override the default item renderer, certain props like highlightMatchingText and emptyText won't have any effect. Refer to the source code to learn more about the built-in item renderer.
Async Loading
This example shows how to load items asynchronously and display a loading indicator.
<q-combobox
class="w-56"
label="Country"
placeholder="Select a country"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
>
@if (query.isFetching()) {
<div q-combobox-icon q-progress-ring size="xs"></div>
}
</q-combobox>
Virtualized
For large lists, use the virtual prop to improve performance. This virtualizes the visible list items for performant scrolling and searching.
<q-combobox
class="w-56"
label="Users"
placeholder="Search for a username"
virtual
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
Virtual Item Customization
Use the q-combobox-virtual-content and q-combobox-virtual-item directives to customize virtual item rendering.
<q-combobox
class="w-56"
label="Starship"
placeholder="Search for a Starship"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
>
@if (query.isFetching()) {
<div q-combobox-icon q-progress-ring size="xs"></div>
}
<div q-combobox-virtual-content [virtualOptions]="virtualOptions">
<div q-combobox-empty>No results found</div>
<ng-container *comboboxVirtualizer="let virtualizer">
@for (
virtualItem of virtualizer.getVirtualItems();
track virtualItem.key
) {
@let item = listCollection.collection().items.at(virtualItem.index);
<div
q-combobox-virtual-item
style="height: 52px"
[virtualItem]="virtualItem"
>
<div class="flex flex-col gap-0.5" q-combobox-item-text>
<div class="font-body-xs-bold">{{ item!.name }}</div>
<div class="font-body-xs overflow-x-hidden text-ellipsis">
{{ item!.url }}
</div>
</div>
<span q-combobox-item-indicator></span>
</div>
}
</ng-container>
</div>
</q-combobox>
Sizes
This component supports three size options to accommodate different layout densities and design requirements.
The available sizes are sm, md, and lg. The default size is md.
import {Component} from "@angular/core"
import {useListCollection} from "@qualcomm-ui/angular-core/collection"
import {ComboboxModule} from "@qualcomm-ui/angular/combobox"
import type {ComboboxInputValueChangeDetails} from "@qualcomm-ui/core/combobox"
const cityList = ["San Diego", "Dallas", "Denver"]
@Component({
imports: [ComboboxModule],
selector: "combobox-sizes-demo",
template: `
<div class="flex flex-col items-center gap-4">
<q-combobox
aria-label="City"
class="w-40"
placeholder="sm"
size="sm"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
<q-combobox
aria-label="City"
class="w-48"
placeholder="md"
size="md"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
<q-combobox
aria-label="City"
class="w-56"
placeholder="lg"
size="lg"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
</div>
`,
})
export class ComboboxSizesDemo {
readonly listCollection = useListCollection<string>({
filter: "contains",
items: cityList,
})
onInputChange(event: ComboboxInputValueChangeDetails) {
this.listCollection.filterText.set(event.inputValue)
}
}Content Height
Change the height of the item container by adjusting the style of the Content element.
import {Component} from "@angular/core"
import {useListCollection} from "@qualcomm-ui/angular-core/collection"
import {PortalDirective} from "@qualcomm-ui/angular-core/portal"
import {ComboboxModule} from "@qualcomm-ui/angular/combobox"
import type {ComboboxInputValueChangeDetails} from "@qualcomm-ui/core/combobox"
import {countries} from "./country-list"
@Component({
imports: [ComboboxModule, PortalDirective],
selector: "combobox-max-height-demo",
template: `
<div
class="w-56"
placeholder="Select a country"
q-combobox-root
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
>
<label q-combobox-label>Country</label>
<div q-combobox-control>
<input q-combobox-input />
<button q-combobox-clear-trigger></button>
<div q-combobox-trigger></div>
</div>
<ng-template qPortal>
<div q-combobox-positioner>
<div q-combobox-content style="max-height: 200px">
<div q-combobox-empty>No results found</div>
<q-combobox-items />
</div>
</div>
</ng-template>
</div>
`,
})
export class ComboboxMaxHeightDemo {
readonly listCollection = useListCollection<string>({
filter: "contains",
items: countries,
})
onInputChange(event: ComboboxInputValueChangeDetails) {
this.listCollection.filterText.set(event.inputValue)
}
}Controlled State
Set the initial value using the defaultValue prop. Use valueChanged to listen to value changes. For fine-grained control, refer to the Forms section below.
<q-combobox
aria-label="Country"
class="w-48"
placeholder="Select a country"
[collection]="listCollection.collection()"
[(ngModel)]="value"
(inputValueChanged)="onInputChange($event)"
/>
States
The following shows how the Combobox component appears in each interactive state.
import {Component} from "@angular/core"
import {useListCollection} from "@qualcomm-ui/angular-core/collection"
import {ComboboxModule} from "@qualcomm-ui/angular/combobox"
import {countries} from "./country-list"
@Component({
imports: [ComboboxModule],
selector: "combobox-states-demo",
template: `
<div class="flex flex-col items-start gap-4">
<q-combobox
class="w-48"
disabled
label="Disabled"
placeholder="Select a country"
[collection]="listCollection.collection()"
/>
<q-combobox
class="w-48"
label="Read Only"
placeholder="Select a country"
readOnly
[collection]="listCollection.collection()"
/>
<q-combobox
class="w-48"
invalid
label="Invalid"
placeholder="Select a country"
[collection]="listCollection.collection()"
/>
</div>
`,
})
export class ComboboxStatesDemo {
readonly listCollection = useListCollection<string>({
filter: "contains",
items: countries,
})
}Error Text and Indicator
Error messages are displayed using two props:
The error text and indicator will only render when invalid is true.
<q-combobox
aria-label="Country"
class="w-48"
errorText="You must select a country"
invalid
placeholder="Select a country"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
Hint Text
Use the hint prop to provide additional context or instructions below the combobox.
<q-combobox
class="w-56"
hint="Choose your country of residence"
label="Country"
placeholder="Select a country"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
Within Dialog
To use the Combobox within a Dialog, you need to avoid portalling the item dropdown. To do this using the simple API, set disablePortal property to true:
<q-combobox
aria-label="Country"
class="w-48"
disablePortal
placeholder="Select a country"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
Within Popover
Like with the Dialog, you need to avoid portalling the q-combobox-positioner:
<div q-popover>
<div q-popover-anchor>
<button q-button q-popover-trigger variant="outline">Click Me</button>
</div>
<q-combobox
disablePortal
label="Country"
placeholder="Search for a country"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
</div>
Forms
- All form control values are arrays to support multiple selections.
- The value of the form control is an array of type
T, whereTis the type of the items in your combobox collection.
Template Forms
Strings
readonly value = signal<string[]>([countries[0]])
Objects
readonly value = signal<Country[]>([{id: "US", name: "United States"}])
States
When using template forms, the disabled, readOnly, and invalid properties govern the interactive state of the control.
<q-combobox
disabled
label="Disabled"
placeholder="Disabled"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
<q-combobox
label="Read only"
placeholder="Read only"
readOnly
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
<q-combobox
errorText="Invalid"
invalid
label="Invalid"
placeholder="Invalid"
[collection]="listCollection.collection()"
(inputValueChanged)="onInputChange($event)"
/>
Required
When using template forms, pass the required property to the form control. This applies the RequiredValidator and MinLengthValidator to the form control.
<q-combobox
class="w-48"
errorText="Please select a country"
label="Country"
placeholder="Select a country"
required
[collection]="listCollection.collection()"
[(ngModel)]="value"
(inputValueChanged)="onInputChange($event)"
/>
Reactive Forms
Strings
readonly formControl = new FormControl<string[]>([countries[0]])
Objects
readonly formControl = new FormControl<Country[]>([
{id: "US", name: "United States"},
])
State Guidelines
The disabled, invalid, and required properties have no effect when using Reactive Forms. Use the equivalent Reactive Form bindings instead:
disabledField = new FormControl([])
invalidField = new FormControl([], {
validators: [Validators.required, Validators.minLength(1)],
})
requiredField = new FormControl([], {
validators: [Validators.required, Validators.minLength(1)],
})
ngOnInit() {
this.disabledField.disable()
this.invalidField.markAsDirty()
}
Explorer
API
<q-combobox>
The simple combobox extends the q-combobox-root directive with the following properties:
stringstringbooleantrue, renders a clear button that resets the input value on click.
The button only appears when the input has a value.booleanstringstring<div q-combobox-error-text>...</div>
booleantrue to highlight option text matches during filtering.string<div q-combobox-hint>...</div>
string<div q-combobox-label>...</div>
booleantrue, the list items will be virtually rendered. Useful for large lists.Composite API
q-combobox-root
booleanbooleanbooleanbooleanbooleanstringstringbooleanInputSigna'ltr' | 'rtl'
booleanboolean() =>
| Node
| ShadowRoot
| Document
string| LucideIconData
| string
stringboolean| 'none'
| 'autohighlight'
| 'autocomplete'
stringbooleanbooleanbooleanbooleanstringbooleanbooleanbooleanbooleanstringPositioningOptionsbooleanbooleanboolean(details: {
getElement: () => HTMLElement
immediate?: boolean
index: number
}) => void
| 'replace'
| 'clear'
| 'preserve'
-
replace: The selected item string is set as the input value-
clear: The input value is cleared-
preserve: The input value is preserved| 'checkmark'
| 'checkbox'
| 'sm'
| 'md'
| 'lg'
boolean{
clearTriggerLabel?: string
triggerLabel?: string
}
booleanvoidCustomEvent<{
event?: E
}>
{
value: string
} & {
highlightedItem: T
highlightedValue: string
}
{
inputValue: string
reason?:
| 'input-change'
| 'item-select'
| 'clear-trigger'
| 'script'
| 'interact-outside'
}
| CustomEvent<{event?: E}>
| CustomEvent<{event?: E}>
{
open: boolean
reason?:
| 'input-click'
| 'trigger-click'
| 'script'
| 'arrow-key'
| 'input-change'
| 'interact-outside'
| 'escape-key'
| 'item-select'
| 'clear-trigger'
| 'input-focus'
}
CustomEvent<{
event?: E
}>
{
items: Array<T>
value: string[]
}
QdsComboboxRootBindingsq-combobox-label
stringclass'qui-select__label'data-combobox-part'label'data-disableddata-focusdata-invaliddata-readonlydata-requiredq-combobox-control
stringclass'qui-select__control'data-combobox-part'control'data-disableddata-focusdata-invaliddata-size| 'sm'
| 'md'
| 'lg'
data-state| 'open'
| 'closed'
q-combobox-input
stringstringstringclass'qui-input__input'data-autofocusdata-combobox-part'input'data-invaliddata-size| 'sm'
| 'md'
| 'lg'
data-state| 'open'
| 'closed'
q-combobox-trigger
booleanstringclass'qui-select__indicator'data-combobox-part'trigger'data-disableddata-focusabledata-invaliddata-readonlydata-size| 'sm'
| 'md'
| 'lg'
data-state| 'open'
| 'closed'
tabIndex-1
q-combobox-clear-trigger
stringclass'qui-select__clear-trigger'data-combobox-part'clear-trigger'data-invaliddata-size| 'sm'
| 'md'
| 'lg'
hiddenbooleantabIndex-1
q-combobox-positioner
stringq-combobox-content
stringclass'qui-select__content'data-combobox-part'content'data-disableddata-emptydata-focus-visibledata-placement| 'bottom'
| 'bottom-end'
| 'bottom-start'
| 'left'
| 'left-end'
| 'left-start'
| 'right'
| 'right-end'
| 'right-start'
| 'top'
| 'top-end'
| 'top-start'
data-state| 'open'
| 'closed'
hiddenbooleantabIndex-1
q-combobox-item
anybooleanclass'qui-select__item'data-combobox-part'item'data-disableddata-highlighteddata-selection-indicator| 'checkmark'
| 'checkbox'
data-size| 'sm'
| 'md'
| 'lg'
data-state| 'checked'
| 'unchecked'
data-valuestringtabIndex-1
q-combobox-item-indicator
data-combobox-part'item-indicator'data-state| 'checked'
| 'unchecked'
hiddenbooleanq-combobox-item-text
class'qui-select__item-text'data-combobox-part'item-text'data-disableddata-highlighteddata-state| 'checked'
| 'unchecked'
q-combobox-hint
stringclass'qui-input__hint'data-combobox-part'hint'data-disabledhiddenbooleanq-combobox-error-text
| string
| LucideIconData
stringclass'qui-input__error-text'data-combobox-part'error-text'hiddenbooleanq-combobox-error-indicator
| LucideIconData
| string
data-combobox-part'error-indicator'hiddenbooleanData Structures
ListCollection
The following describes the member variables and methods of the ListCollection class. The Combobox component uses an instance of this class as input:
@Component({
template: `
<q-combobox [collection]="collection" />
`,
})
export class MyComponent {
collection = comboboxCollection({
items: [
// ...
],
})
}Note that the ListCollection accepts a single generic type parameter, T, which is the type of each list item used in the collection. This can be a string or an object.
Constructor
(
item: T,
index: number,
) => string
| string[]
| 'desc'
| 'asc'
| ((
a: string,
b: string,
) => number)
(
item: T,
) => boolean
(
item: T,
) => string
| Iterable<T, any, any>
| Readonly<
Iterable<T, any, any>
>
(
item: T,
) => string
(
items: Array<T>,
) => ListCollection<T>
(
index: number,
) => T
(
a: string,
b: string,
) => -1 | 0 | 1
(items Array<T>,) => ListCollection<T>
(
fn: (
itemString: string,
index: number,
item: T,
) => boolean,
) => ListCollection<T>
(
value: string,
) => T
(
values: string[],
) => Array<T>
string(
T,
) => boolean
(
T,
) => string
(
value: string,
step: number,
clamp: boolean,
) => string
(
value: string,
step: number,
clamp: boolean,
) => string
(
from: string,
to: string,
) => string[]
(
items: Array<T>,
) => string[]
() => Array<
[string, Array<T>]
>
(
value: string,
) => boolean
(
T,
) => boolean
(
value: string,
) => number
(
index: number,
items: Array<T>,
) => ListCollection<T>
(
value: string,
items: Array<T>,
) => ListCollection<T>
(
value: string,
items: Array<T>,
) => ListCollection<T>
(
any,
) => boolean
Array<T>
string(
value: string,
toIndex: number,
) => ListCollection<T>
(
value: string,
values: string[],
) => ListCollection<T>
(
value: string,
values: string[],
) => ListCollection<T>
(
items: Array<T>,
) => ListCollection<T>
(
itemsOrValues: Array<
string | T
>,
) => ListCollection<T>
(
fromIndex: number,
toIndex: number,
) => ListCollection<T>
(
queryString: string,
{
currentValue: string
state: {
keysSoFar: string
timer: number
}
timeout?: number
},
) => string
(
items: Array<T>,
) => ListCollection<T>
number(
values: string[],
) => string[]
(
value: string,
) => string
(
T,
) => string
(
items: Array<T>,
separator: string,
) => string
(value: string[],separator string,) => string
() => {
first: string
last: string
size: number
}
() => string
(
value: string,
T,
) => ListCollection<T>
(
value: string,
T,
mode: 'append' | 'prepend',
) => ListCollection<T>
ComboboxPositioningOptions
numberstring[data-menu-part=arrow]).() =>
| 'clippingAncestors'
| Element
| Array<Element>
| {
height: number
width: number
x: number
y: number
}
boolean| boolean
| Array<
| 'bottom'
| 'bottom-end'
| 'bottom-start'
| 'left'
| 'left-end'
| 'left-start'
| 'right'
| 'right-end'
| 'right-start'
| 'top'
| 'top-end'
| 'top-start'
>
(
element:
| HTMLElement
| VirtualElement,
) => {
height?: number
width?: number
x?: number
y?: number
}
numberboolean| boolean
| {
ancestorResize?: boolean
ancestorScroll?: boolean
animationFrame?: boolean
elementResize?: boolean
layoutShift?: boolean
}
{
crossAxis?: number
mainAxis?: number
}
(
data: ComputePositionReturn,
) => void
(data: {
placed: boolean
}) => void
numberboolean| 'bottom'
| 'bottom-end'
| 'bottom-start'
| 'left'
| 'left-end'
| 'left-start'
| 'right'
| 'right-end'
| 'right-start'
| 'top'
| 'top-end'
| 'top-start'
booleannumberboolean| 'absolute'
| 'fixed'
(data: {
updatePosition: () => Promise<void>
}) => void | Promise<void>