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 {Select} from "@qualcomm-ui/react/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 input element for accessibility. If you omit the label, you should provide the aria-label prop to give your select an accessible name.
<q-select
aria-label="City"
class="w-48"
placeholder="Select a city"
[collection]="cityCollection"
/>
NOTE
If you're using the composite API, provide the aria-label attribute to the q-select-trigger element instead.
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.
<q-select
class="w-72"
label="City"
multiple
placeholder="Select a city"
[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()
}
API
<q-select>
The simple select extends the q-select-root directive with the following properties:
| Prop | Type | Default |
|---|---|---|
ARIA label applied to the control element. Use this if you omit the label | string | |
When true, renders a clear button that resets the input value on click.
The button only appears when the input has a value. | boolean | true |
Set to true to disable portalling behavior for the select dropdown content. | boolean | |
string | ||
To customize the element, provide it using the directive instead: <div q-select-error-text>...</div> | ||
Optional hint describing the element. This element is automatically
associated with the component's input element for accessibility. | string | |
To customize the element, provide it using the directive instead: <div q-select-hint>...</div> | ||
Optional label describing the element. Recommended. This element is
automatically associated with the component's input element for
accessibility. | string | |
To customize the element, provide it using the directive instead: <div q-select-label>...</div> | ||
stringbooleantrue, 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
| Prop | Type | Default |
|---|---|---|
The item collection | ||
Whether the select should close after an item is selected | boolean | true |
The initial value of the highlighted item when opened.
Use when you don't need to control the highlighted value of the select. | string | |
Whether the select's open state is controlled by the user | boolean | |
The initial value of the input when rendered. Use when you don't need to
control the value of the input. This property will be ignored if you
opt into controlled state via form control bindings. | InputSigna | |
Whether the value can be cleared by clicking the selected item. This is only applicable for single selection. | boolean | |
The document's text/writing direction. | 'ltr' | 'rtl' | "ltr" |
Controls whether the input is disabled in template-driven forms. When true,
prevents user interaction and applies visual styling to indicate the disabled
state. | boolean | |
A root node to correctly resolve the Document in custom environments. i.e.,
Iframes, Electron. | () => | |
The controlled key of the highlighted item | string | |
lucide icon, positioned at the start of the trigger element. | | LucideIconData | |
id attribute. If
omitted, a unique identifier will be generated for accessibility.) | string | |
Controls the visual error state of the input. When true, applies semantic error
styling to indicate validation failure. | boolean | |
Whether to loop the keyboard navigation through the options | boolean | false |
Whether to allow multiple selection | boolean | false |
The name of the input field. Useful for form submission. | string | |
Whether the select menu is open | boolean | |
Placeholder text to display when no value is selected. | string | "Select option" |
The positioning options of the menu. | PositioningOptions | |
Whether the input is read-only. When true, prevents user interaction while
keeping the input focusable and visible. | boolean | |
Controls whether the input is required in template-driven forms. When
true, the input must have a value for form validation to pass. | boolean | |
Function to scroll to a specific index | (details: { | |
The size of the select and its elements. Governs properties like font size,
item padding, and icon sizes. | | 'sm' | 'md' |
Function called when the focus is moved outside the component | CustomEvent<{ | |
The callback fired when the highlighted item changes. | { | |
Function called when an interaction happens outside the component | | CustomEvent<{event?: E}> | |
Function invoked when the popup opens or closes | boolean | |
Function called when the pointer is pressed down outside the component | CustomEvent<{ | |
Function called when an item is selected | { | |
The callback fired when the selected item changes. | { |
booleanstringbooleanInputSignabooleanThis is only applicable for single selection.
'ltr' | 'rtl'
boolean() =>
| Node
| ShadowRoot
| Document
string| LucideIconData
| string
stringbooleanbooleanbooleanstringbooleanstringPositioningOptionsbooleanboolean(details: {
immediate?: boolean
index: number
}) => void
| '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
| Prop | Type |
|---|---|
id attribute. If
omitted, a unique identifier will be generated for accessibility. | string |
string| Attribute / Property | Value |
|---|---|
class | 'qui-select__label' |
data-disabled | |
data-invalid | |
data-part | 'label' |
data-readonly | |
data-scope | 'select' |
class'qui-select__label'data-disableddata-invaliddata-part'label'data-readonlydata-scope'select'q-select-trigger
SelectTriggerDirectiveQdsSelectTriggerBindings, SelectTriggerBindingsq-select-indicator
| Prop | Type | Default |
|---|---|---|
Indicator icon. | | LucideIconData | ChevronDown |
| LucideIconData
| string
| Attribute / Property | Value |
|---|---|
class | 'qui-select__indicator' |
data-disabled | |
data-invalid | |
data-part | 'indicator' |
data-readonly | |
data-scope | 'select' |
data-size | | 'sm' |
data-state | | 'open' |
tabIndex | -1 |
class'qui-select__indicator'data-disableddata-invaliddata-part'indicator'data-readonlydata-scope'select'data-size| 'sm'
| 'md'
| 'lg'
data-state| 'open'
| 'closed'
tabIndex-1
q-select-value-text
| Attribute / Property | Value |
|---|---|
class | 'qui-select__value-text' |
data-disabled | |
data-focus | |
data-invalid | |
data-multiple | |
data-part | 'value-text' |
data-scope | 'select' |
data-size | | 'sm' |
class'qui-select__value-text'data-disableddata-focusdata-invaliddata-multipledata-part'value-text'data-scope'select'data-size| 'sm'
| 'md'
| 'lg'
q-select-clear-trigger
| Prop | Type |
|---|---|
id attribute. If
omitted, a unique identifier will be generated for accessibility. | string |
string| Attribute / Property | Value |
|---|---|
class | 'qui-select__clear-trigger' |
data-invalid | |
data-part | 'clear-trigger' |
data-scope | 'select' |
data-size | | 'sm' |
hidden | boolean |
class'qui-select__clear-trigger'data-invaliddata-part'clear-trigger'data-scope'select'data-size| 'sm'
| 'md'
| 'lg'
hiddenbooleanq-select-positioner
| Prop | Type |
|---|---|
id attribute. If
omitted, a unique identifier will be generated for accessibility. | string |
string| Attribute / Property | Value |
|---|---|
class | 'qui-select__positioner' |
data-part | 'positioner' |
data-scope | 'select' |
style |
class'qui-select__positioner'data-part'positioner'data-scope'select'styleq-select-content
| Prop | Type |
|---|---|
id attribute. If
omitted, a unique identifier will be generated for accessibility. | string |
string| Attribute / Property | Value |
|---|---|
class | 'qui-select__content' |
data-activedescendant | string |
data-focus-visible | |
data-part | 'content' |
data-placement | | 'bottom' |
data-scope | 'select' |
data-state | | 'open' |
hidden | boolean |
tabIndex | 0 |
class'qui-select__content'data-activedescendantstringdata-focus-visibledata-part'content'data-placement| 'bottom'
| 'bottom-end'
| 'bottom-start'
| 'left'
| 'left-end'
| 'left-start'
| 'right'
| 'right-end'
| 'right-start'
| 'top'
| 'top-end'
| 'top-start'
data-scope'select'data-state| 'open'
| 'closed'
hiddenbooleantabIndex0q-select-item
| Prop | Type |
|---|---|
The item to render, from the collection | any |
Whether hovering outside should clear the highlighted state | boolean |
anyboolean| Attribute / Property | Value |
|---|---|
class | 'qui-select__item' |
data-disabled | |
data-highlighted | |
data-part | 'item' |
data-scope | 'select' |
data-size | | 'sm' |
data-state | | 'checked' |
data-value | string |
class'qui-select__item'data-disableddata-highlighteddata-part'item'data-scope'select'data-size| 'sm'
| 'md'
| 'lg'
data-state| 'checked'
| 'unchecked'
data-valuestringq-select-item-indicator
| Attribute / Property | Value |
|---|---|
data-part | 'item-indicator' |
data-scope | 'select' |
data-state | | 'checked' |
hidden | boolean |
data-part'item-indicator'data-scope'select'data-state| 'checked'
| 'unchecked'
hiddenbooleanq-select-item-text
| Attribute / Property | Value |
|---|---|
data-disabled | |
data-highlighted | |
data-part | 'item-text' |
data-scope | 'select' |
data-state | | 'checked' |
data-disableddata-highlighteddata-part'item-text'data-scope'select'data-state| 'checked'
| 'unchecked'
q-select-hidden-select
| Prop | Type |
|---|---|
id attribute. If
omitted, a unique identifier will be generated for accessibility. | string |
string| Attribute / Property | Value |
|---|---|
class | 'qui-select__hidden-select' |
data-part | 'hidden-select' |
data-scope | 'select' |
style | |
tabIndex | -1 |
class'qui-select__hidden-select'data-part'hidden-select'data-scope'select'styletabIndex-1
q-select-hint
| Prop | Type |
|---|---|
id attribute. If
omitted, a unique identifier will be generated for accessibility. | string |
string| Attribute / Property | Value |
|---|---|
data-disabled | |
data-part | 'hint' |
data-scope | 'select' |
hidden | boolean |
data-disableddata-part'hint'data-scope'select'hiddenbooleanq-select-error-text
| Prop | Type |
|---|---|
Optional error indicator icon. | | string |
id attribute. If
omitted, a unique identifier will be generated for accessibility. | string |
| string
| LucideIconData
string| Attribute / Property | Value |
|---|---|
data-part | 'error-text' |
data-scope | 'select' |
hidden | boolean |
data-part'error-text'data-scope'select'hiddenbooleanq-select-error-indicator
| Prop | Type | Default |
|---|---|---|
lucide-angular icon | | LucideIconData | CircleAlert |
| LucideIconData
| string
| Attribute / Property | Value |
|---|---|
data-part | 'error-indicator' |
data-scope | 'select' |
hidden | boolean |
data-part'error-indicator'data-scope'select'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
| Prop | Type |
|---|---|
Function to group items | ( |
Function to sort items | | string[] |
Function to determine if a node is disabled. | ( |
Function to get the item's label. | ( |
The options of the select | | Iterable<T, any, any> |
Function to get the item's unique value. | ( |
(
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
| Prop | Type |
|---|---|
Append items to the collection | ( |
Get the item based on its index | ( |
Compare two values | ( |
Copy the collection | (items Array<T>,) => ListCollection<T> |
Filter the collection | ( |
Get the item based on its value | ( |
Get the items based on its values | ( |
Returns the first value in the collection | string |
Whether an item is disabled | ( |
Convert an item to a value | ( |
Returns the next value in the collection | ( |
Returns the previous value in the collection | ( |
Get the range of values between two values | ( |
Returns all the values in the collection | ( |
Group items by the groupBy function provided in options
Returns an array of [groupKey, items] tuples | () => Array< |
Whether the collection has a value | ( |
Whether the collection has an item | ( |
Get the index of an item based on its key | ( |
Insert items at a specific index | ( |
Insert items after a specific value | ( |
Insert items before a specific value | ( |
Check if the collection is equal to another collection
| ( |
The items in the collection | Array<T> |
Returns the last value in the collection | string |
Move an item to a specific index | ( |
Move items after a specific value | ( |
Move items before a specific value | ( |
Prepend items to the collection | ( |
Remove items from the collection | ( |
Reorder items | ( |
Search for a value based on a query | ( |
Function to update the collection items | ( |
Returns the number of items in the collection | number |
Sort the values based on their index | ( |
Convert a value to a string | ( |
Convert an item to a string | ( |
Convert an array of items to a string | ( |
Convert an array of items to a string | (value: string[],separator string,) => string |
Convert the collection to a JSON object | () => { |
Convert the collection to a string | () => string |
Update an item in the collection | ( |
Update an item in the collection if it exists, otherwise append it | ( |
(
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
- items:The items in the collection
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
| Prop | Type | Default |
|---|---|---|
The minimum padding between the arrow and the floating element's corner. | number | 4 |
The overflow boundary of the reference element | () => | |
Whether the popover should fit the viewport. | boolean | |
Whether to flip the placement when the floating element overflows the boundary. | | boolean | true |
Function that returns the anchor rect | ( | |
The main axis offset or gap between the reference and floating element | number | 2 |
Whether the popover should be hidden when the reference element is detached | boolean | |
Options to activate auto-update listeners | | boolean | true |
The offset of the floating element | { | |
Function called when the placement is computed | ( | |
Function called when the floating element is positioned or not | (data: { | |
The virtual padding around the viewport edges to check for overflow | number | |
Whether the floating element can overlap the reference element | boolean | false |
The initial placement of the floating element | | 'bottom' | 'bottom-start' |
Whether to make the floating element same width as the reference element | boolean | true |
The secondary axis offset or gap between the reference and floating elements | number | |
Whether the popover should slide when it overflows. | boolean | |
The strategy to use for positioning | | 'absolute' | 'absolute' |
A callback that will be called when the popover needs to calculate its
position. | (data: { |
number() =>
| '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>