Overview for listbox
The @angular/cdk/listbox
module provides directives to help create custom listbox interactions
based on the WAI ARIA listbox pattern.
By using @angular/cdk/listbox
you get all the expected behaviors for an accessible experience,
including bidi layout support, keyboard interaction, and focus management. All directives apply
their associated ARIA roles to their host element.
Supported ARIA Roles
The directives in @angular/cdk/listbox
set the appropriate roles on their host element.
Directive | ARIA Role |
---|---|
cdkOption | option |
cdkListbox | listbox |
CSS Styles and Classes
The @angular/cdk/listbox
is designed to be highly customizable to your needs. It therefore does not
make any assumptions about how elements should be styled. You are expected to apply any required
CSS styles, but the directives do apply CSS classes to make it easier for you to add custom styles.
The available CSS classes are listed below, by directive.
Directive | CSS Class | Applied... |
---|---|---|
cdkOption | .cdk-option | Always |
cdkOption | .cdk-option-active | If the option is active |
cdkListbox | .cdk-listbox | Always |
In addition to CSS classes, these directives add aria attributes that can be targeted in CSS.
Directive | Attribute Selector | Applied... |
---|---|---|
cdkOption | [aria-disabled="true"] | If the option is disabled |
cdkOption | [aria-disabled="false"] | If the option is not disabled |
cdkOption | [aria-selected="true"] | If the option is selected |
cdkOption | [aria-selected="false"] | If the option is not selected |
cdkListbox | [aria-disabled="true"] | If the listbox is disabled |
cdkListbox | [aria-disabled="false"] | If the listbox is not disabled |
cdkListbox | [aria-multiselectable="true"] | If the listbox is multiple selection |
cdkListbox | [aria-multiselectable="false"] | If the listbox is single selection |
cdkListbox | [aria-orientation="horizontal"] | If the listbox is oriented horizontally |
cdkListbox | [aria-orientation="vertical"] | If the listbox is oriented vertically |
Getting started
Import the CdkListboxModule
into the NgModule
in which you want to create a listbox. You can
then apply listbox directives to build your custom listbox. A typical listbox consists of the
following directives:
cdkListbox
- Added to the container element containing the options to be selectedcdkOption
- Added to each selectable option in the listbox
<label class="example-listbox-label" id="example-fav-color-label">
Favorite color
</label>
<ul cdkListbox
aria-labelledby="example-fav-color-label"
class="example-listbox">
<li cdkOption="red" class="example-option">Red</li>
<li cdkOption="green" class="example-option">Green</li>
<li cdkOption="blue" class="example-option">Blue</li>
</ul>
Option values
Each option in a listbox is bound to the value it represents when selected, e.g.
<li cdkOption="red">Red</li>
. Within a single listbox, each option must have a unique value. If
an option is not explicitly given a value, its value is considered to be ''
(empty string), e.g.
<li cdkOption>No color preference</li>
.
<li cdkOption="red" class="example-option">Red</li>
Single vs multiple selection
Listboxes only support a single selected option at a time by default, but adding
cdkListboxMultiple
will enable selecting more than one option.
<label class="example-listbox-label" id="example-fav-cuisine-label">
Favorite cuisines
</label>
<ul cdkListbox
cdkListboxMultiple
aria-labelledby="example-fav-cuisine-label"
class="example-listbox">
<li cdkOption="chinese" class="example-option">Chinese</li>
<li cdkOption="french" class="example-option">French</li>
<li cdkOption="italian" class="example-option">Italian</li>
<li cdkOption="japanese" class="example-option">Japanese</li>
</ul>
Listbox value
The listbox's value is an array containing the values of the selected option(s). This is true even
for the single selection listbox, whose value is an array containing a single element. The listbox's
value can be bound using [cdkListboxValue]
and (cdkListboxValueChange)
.
<label class="example-listbox-label" id="example-starter-pokemon-label">
Starter Pokemon
</label>
<ul cdkListbox
[cdkListboxValue]="starter"
(cdkListboxValueChange)="starter = $event.value"
aria-labelledby="example-starter-pokemon-label"
class="example-listbox">
@for (pokemon of starters; track pokemon) {
<li [cdkOption]="pokemon" class="example-option">{{pokemon}}</li>
}
</ul>
Internally the listbox compares the listbox value against the individual option values using
Object.is
to determine which options should appear selected. If your option values are complex
objects, you should provide a custom comparison function instead. This can be set via the
cdkListboxCompareWith
input on the listbox.
<label class="example-listbox-label" id="example-appointment-label">
Appointment Time
</label>
<ul cdkListbox
[cdkListboxValue]="appointment"
[cdkListboxCompareWith]="compareDate"
(cdkListboxValueChange)="appointment = $event.value"
aria-labelledby="example-appointment-label"
class="example-listbox">
@for (time of slots; track time) {
<li [cdkOption]="time" class="example-option">{{formatTime(time)}}</li>
}
</ul>
Angular Forms support
The CDK Listbox supports both template driven forms and reactive forms.
<label class="example-listbox-label" id="example-toppings-label">
Choose Toppings
</label>
<ul cdkListbox
cdkListboxMultiple
[(ngModel)]="order"
aria-labelledby="example-toppings-label"
class="example-listbox">
@for (topping of toppings; track topping) {
<li [cdkOption]="topping" class="example-option">{{topping}}</li>
}
</ul>
<label class="example-listbox-label" id="example-language-label">
Preferred Language
</label>
<ul cdkListbox
[formControl]="languageCtrl"
aria-labelledby="example-language-label"
class="example-listbox">
@for (language of languages; track language) {
<li [cdkOption]="language" class="example-option">{{language}}</li>
}
</ul>
Disabling options
You can disable options for selection by setting cdkOptionDisabled
.
In addition, the entire listbox control can be disabled by setting cdkListboxDisabled
on the
listbox element.
<label class="example-listbox-label" id="example-wine-type-label">
Wine Selection
</label>
<ul cdkListbox
[cdkListboxDisabled]="!canDrinkCtrl.value"
aria-labelledby="example-wine-type-label"
class="example-listbox">
<li cdkOption="cabernet"
class="example-option">
Cabernet Sauvignon
</li>
<li cdkOption="syrah"
class="example-option">
Syrah
</li>
<li cdkOption="zinfandel"
cdkOptionDisabled
class="example-option">
Zinfandel <span class="example-sold-out">(sold out)</span>
</li>
<li cdkOption="riesling"
class="example-option">
Riesling
</li>
</ul>
Accessibility
The directives defined in @angular/cdk/listbox
follow accessibility best practices as defined
in the ARIA spec. Keyboard interaction is supported as defined in the
ARIA listbox keyboard interaction spec without the optional selection follows focus
logic (TODO: should we make this an option?).
Listbox label
Always give the listbox a meaningful label for screen readers. If your listbox has a visual label,
you can associate it with the listbox using aria-labelledby
, otherwise you should provide a
screen-reader-only label with aria-label
.
Roving tabindex vs active descendant
By default, the CDK listbox uses the roving tabindex strategy to manage focus.
If you prefer to use the aria-activedescendant strategy instead, set
useActiveDescendant=true
on the listbox.
<label class="example-listbox-label" id="example-spatula-label">
Spatula Features
</label>
<ul cdkListbox
cdkListboxMultiple
cdkListboxUseActiveDescendant
aria-labelledby="example-spatula-label"
class="example-listbox">
@for (feature of features; track feature) {
<li [cdkOption]="feature" class="example-option">{{feature}}</li>
}
</ul>
Orientation
Listboxes assume a vertical orientation by default, but can be customized by setting the
cdkListboxOrientation
input. Note that this only affects the keyboard navigation. You
will still need to adjust your CSS styles to change the visual appearance.
<label class="example-listbox-label" id="example-shirt-size-label">
Shirt Size
</label>
<ul cdkListbox
cdkListboxOrientation="horizontal"
aria-labelledby="example-shirt-size-label"
class="example-listbox">
@for (size of sizes; track size) {
<li [cdkOption]="size" class="example-option">{{size}}</li>
}
</ul>
Option typeahead
The CDK listbox supports typeahead based on the option text. If the typeahead text for your options
needs to be different than the display text (e.g. to exclude emoji), this can be accomplished by
setting the cdkOptionTypeaheadLabel
on the option.
<label class="example-listbox-label" id="example-satisfaction-label">
How was your service?
</label>
<ul cdkListbox
aria-labelledby="example-satisfaction-label"
class="example-listbox">
<li
[cdkOption]="1"
cdkOptionTypeaheadLabel="great"
class="example-option">
😀 Great
</li>
<li [cdkOption]="0"
cdkOptionTypeaheadLabel="okay"
class="example-option">
😐 Okay
</li>
<li [cdkOption]="-1"
cdkOptionTypeaheadLabel="bad"
class="example-option">
🙁 Bad
</li>
</ul>
Keyboard navigation options
When using keyboard navigation to navigate through the options, the navigation wraps when attempting
to navigate past the start or end of the options. To change this, set
cdkListboxNavigationWrapDisabled
on the listbox.
Keyboard navigation skips disabled options by default. To change this set
cdkListboxNavigatesDisabledOptions
on the listbox.
<label class="example-listbox-label" id="example-flavor-label">
Flavor
</label>
<ul cdkListbox
cdkListboxNavigatesDisabledOptions
cdkListboxNavigationWrapDisabled
aria-labelledby="example-flavor-label"
class="example-listbox">
<li cdkOption="chocolate"
class="example-option">
Chocolate
</li>
<li cdkOption="pumpkin-spice"
cdkOptionDisabled
class="example-option">
Pumpkin Spice (seasonal)
</li>
<li cdkOption="strawberry"
class="example-option">
Strawberry
</li>
<li cdkOption="vanilla"
class="example-option">
Vanilla
</li>
</ul>