Select
The select component provides a streamlined way for users to pick a single option from a collapsible list of choices. It conserves screen space while offering clear visual feedback and maintaining accessibility standards.
import {SelectModule} from "@qualcomm-ui/angular/select"Overview
- Each select uses the
selectCollectionhelper to manage the list of options. This creates aListCollectioninstance, documented below.
Examples
Simple
The simple API provides a standalone component with built-in layout.
<q-select
class="w-48"
label="City"
placeholder="Select a city"
[collection]="cityCollection"
/>
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-48"
placeholder="Select a city"
q-select-root
[collection]="cityCollection"
>
<div q-select-label>City</div>
<div q-select-control>
<span q-select-value-text></span>
<button q-select-clear-trigger></button>
<button q-select-indicator></button>
<div q-select-error-indicator></div>
</div>
<select q-select-hidden-select></select>
<ng-template qPortal>
<div q-select-positioner>
<div q-select-content>
@for (item of cityCollection.items; track item) {
<div q-select-item [item]="item">
<span q-select-item-text>
{{ cityCollection.stringifyItem(item) }}
</span>
<span q-select-item-indicator></span>
</div>
}
</div>
</div>
</ng-template>
</div>
ARIA Label
The Select's label is automatically associated with the control for accessibility. If you omit the label, give the control an accessible name with aria-label or aria-labelledby.
<q-select
aria-label="City"
class="w-48"
placeholder="Select a city"
[collection]="cityCollection"
/>
NOTE
Use aria-label or aria-labelledby on the simple <q-select> to forward an accessible name to the internal control. The q-select host removes those attributes after forwarding them. If you use the composite API, provide aria-label or aria-labelledby on the q-select-control element. For dynamic label references, use [aria-labelledby] instead of [attr.aria-labelledby] so the control can treat the value as a consumer-provided override.
Content Width
The positioning.sameWidth property controls whether the width of the select content matches the width of the trigger element. The default is true.
<q-select
class="w-48"
label="City"
placeholder="Select a city"
[collection]="cityCollection"
[positioning]="{sameWidth: false}"
/>
Trigger Icon
Use the icon prop to add an icon to the start of the trigger element. View our Icon documentation to learn more about using icons in QUI.
<q-select
class="w-48"
icon="MapPin"
label="City"
placeholder="Select a city"
[collection]="cityCollection"
/>
Items as Objects
The items prop can be an array of objects. Use the itemLabel and itemValue properties on the selectCollection to specify the label and value of each item.
cityCollection = selectCollection({
itemLabel: (item) => item.name,
items: [
{name: "San Diego", value: "SD"},
{name: "Nashville", value: "NV"},
{name: "Denver", value: "DV"},
{name: "Miami", value: "MI"},
{name: "Las Vegas", value: "LV"},
{name: "New York City", value: "NYC"},
{name: "San Francisco", value: "SF"},
],
itemValue: (item) => item.value,
})
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.
<q-select
aria-label="City"
class="w-40"
placeholder="sm"
size="sm"
[collection]="cityCollection"
/>
<q-select
aria-label="City"
class="w-48"
placeholder="md"
size="md"
[collection]="cityCollection"
/>
<q-select
aria-label="City"
class="w-56"
placeholder="lg"
size="lg"
[collection]="cityCollection"
/>
Content Height
Change the height of the item container by adjusting the style of the Content element.
<q-select
class="w-48"
label="City"
placeholder="Select a city"
[collection]="cityCollection"
>
<div q-select-content style="max-height: 240px">
<q-select-items />
</div>
</q-select>
Multiple Selection
Use the multiple prop to allow multiple selections.
The selectionIndicator prop controls how selected items are displayed:
"checkmark"— Uses a checkmark after the selected options only (default)"checkbox"— Uses a checkbox before each option
<q-select
class="w-72"
label="City"
multiple
placeholder="Select cities"
selectionIndicator="checkbox"
[collection]="cityCollection"
/>
Controlled Dropdown Visibility
Set the initial visibility using the defaultOpen prop, or use open and openChanged to control the dropdown visibility manually. These props follow our controlled state pattern.
<q-select
class="w-48"
label="City"
placeholder="Select a city"
[collection]="cityCollection"
[open]="isOpen()"
(openChanged)="isOpen.set($event)"
/>
Error Text and Indicator
Error messages are displayed using two props:
The error text and indicator will only render when invalid is true.
<q-select
class="w-48"
errorText="Invalid"
invalid
label="City"
[collection]="cityCollection"
/>
Item Customization
When using the simple API, provide the q-select-content directive as a child of the <q-select> component to override the default item renderer. The following example demonstrates how to add a custom icon to each item:
<q-select
class="w-48"
label="Food"
[collection]="cityCollection"
[icon]="valueIcon()"
(valueChanged)="value.set($event.items)"
>
<div q-select-content>
@for (
item of cityCollection.items;
track cityCollection.getItemValue(item)
) {
<div q-select-item [item]="item">
<svg [qIcon]="item.icon"></svg>
<span q-select-item-text>
{{ cityCollection.stringifyItem(item) }}
</span>
<span q-select-item-indicator></span>
</div>
}
</div>
</q-select>
Within Dialog
To use the Select within a Dialog, you need to avoid portalling the item dropdown. To do this using the simple API, set disablePortal property to true:
<q-select
aria-label="City"
class="w-48"
disablePortal
placeholder="Select a city"
[collection]="cityCollection"
/>
Within Popover
Like with the Dialog, you need to avoid portalling the q-select-positioner:
<div q-popover>
<div q-popover-anchor>
<button q-button q-popover-trigger variant="outline">Click Me</button>
</div>
<q-select
disablePortal
label="City"
placeholder="Select a city"
[collection]="cityCollection"
/>
</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 select collection.
Template Forms
Strings
readonly value = signal<string[]>(["San Diego"])
Objects
readonly value = signal<City[]>([{id: "SD", name: "San Diego"}])
States
When using template forms, the disabled, readOnly, and invalid properties govern the interactive state of the control.
<q-select
disabled
label="Disabled"
placeholder="Disabled"
[collection]="cityCollection"
/>
<q-select
label="Read only"
placeholder="Read only"
readOnly
[collection]="cityCollection"
/>
<q-select
errorText="Invalid"
invalid
label="Invalid"
placeholder="Invalid"
[collection]="cityCollection"
/>
Required
When using template forms, pass the required property to the form control. This applies the RequiredValidator and MinLengthValidator to the form control.
<q-select
class="w-48"
errorText="Please select a city"
label="City"
placeholder="Select a city"
required
[collection]="cityCollection"
[(ngModel)]="value"
/>
Reactive Forms
Strings
readonly formControl = new FormControl<string[]>(["San Diego"])
Objects
readonly formControl = new FormControl<City[]>([
{id: "SD", name: "San Diego"},
])
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
Component Anatomy
Hover to highlight, click to view API
API
<q-select>
The simple select extends the q-select-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.booleanstring<div q-select-error-text>...</div>
string<div q-select-hint>...</div>
string<div q-select-label>...</div>
Composite API
q-select-root
booleanstringbooleanInputSignabooleanThis is only applicable for single selection.
'ltr' | 'rtl'
boolean() =>
| Node
| ShadowRoot
| Document
string| LucideIconData
| string
stringbooleanbooleanbooleanstringbooleanstringPositioningOptionsbooleanboolean(details: {
immediate?: boolean
index: number
}) => void
| 'checkmark'
| 'checkbox'
| 'sm'
| 'md'
| 'lg'
CustomEvent<{
event?: E
}>
{
value: string
} & {
highlightedIndex: number
highlightedItem: T
}
| CustomEvent<{event?: E}>
| CustomEvent<{event?: E}>
booleanCustomEvent<{
event?: E
}>
{
value: string
}
{
items: Array<T>
value: string[]
}
q-select-label
stringclass'qui-select__label'data-disableddata-invaliddata-readonlydata-select-part'label'q-select-control
string[attr.aria-label]stringstringclass'qui-select__control'data-disableddata-invaliddata-placeholder-showndata-placement| 'bottom'
| 'bottom-end'
| 'bottom-start'
| 'left'
| 'left-end'
| 'left-start'
| 'right'
| 'right-end'
| 'right-start'
| 'top'
| 'top-end'
| 'top-start'
data-readonlydata-select-part'control'data-size| 'sm'
| 'md'
| 'lg'
data-state| 'open'
| 'closed'
tabIndexnumberq-select-indicator
| LucideIconData
| string
class'qui-select__indicator'data-disableddata-invaliddata-readonlydata-select-part'indicator'data-size| 'sm'
| 'md'
| 'lg'
data-state| 'open'
| 'closed'
tabIndex-1
q-select-value-text
class'qui-select__value-text'data-disableddata-focusdata-invaliddata-multipledata-select-part'value-text'data-size| 'sm'
| 'md'
| 'lg'
q-select-clear-trigger
stringclass'qui-select__clear-trigger'data-invaliddata-select-part'clear-trigger'data-size| 'sm'
| 'md'
| 'lg'
hiddenbooleanq-select-positioner
stringq-select-content
stringclass'qui-select__content'data-activedescendantstringdata-focus-visibledata-placement| 'bottom'
| 'bottom-end'
| 'bottom-start'
| 'left'
| 'left-end'
| 'left-start'
| 'right'
| 'right-end'
| 'right-start'
| 'top'
| 'top-end'
| 'top-start'
data-select-part'content'data-state| 'open'
| 'closed'
hiddenbooleantabIndex0q-select-item
anybooleanclass'qui-select__item'data-disableddata-highlighteddata-select-part'item'data-selection-indicator| 'checkmark'
| 'checkbox'
data-size| 'sm'
| 'md'
| 'lg'
data-state| 'checked'
| 'unchecked'
data-valuestringq-select-item-checkbox
Checkbox-style indicator for select items. Use this instead of q-select-item-indicator when selectionIndicator="checkbox" is set on the root. The checkbox is always visible and fills when the item is selected.
q-select-item-indicator
data-select-part'item-indicator'data-state| 'checked'
| 'unchecked'
hiddenbooleanq-select-item-text
data-disableddata-highlighteddata-select-part'item-text'data-state| 'checked'
| 'unchecked'
q-select-hidden-select
stringclass'qui-select__hidden-select'data-select-part'hidden-select'styletabIndex-1
q-select-hint
stringdata-disableddata-select-part'hint'hiddenbooleanq-select-error-text
| string
| LucideIconData
stringdata-select-part'error-text'hiddenbooleanq-select-error-indicator
| LucideIconData
| string
data-select-part'error-indicator'hiddenbooleanData Structures
ListCollection
The following describes the member variables and methods of the ListCollection class. The Select component uses an instance of this class as input:
@Component({
template: `
<q-select [collection]="collection" />
`,
})
export class MyComponent {
collection = selectCollection({
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>
SelectPositioningOptions
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>
selectionIndicator="checkbox"on the Select root.