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.

The directives in @angular/cdk/listbox set the appropriate roles on their host element.

Directive ARIA Role
cdkOption option
cdkListbox listbox

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

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 selected
  • cdkOption - 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>

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>

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>

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>

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>

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>

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?).

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.

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>

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>

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>

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>
Azure & Blue theme selected.