Listbox Listbox pattern Listbox API Reference Overview A directive that displays a list of options for users to select from, supporting keyboard navigation, single or multiple selection, and screen reader support.
import {Listbox, Option} from '@angular/aria/listbox';
import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [Listbox, Option],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
/** The options available in the listbox. */
options = [
'Option 1',
'Option 2',
'Option 3',
'Option 4',
'Option 5',
'Option 6',
'Option 7',
'Option 8',
];
}
<div class="listbox-container">
<div ngListbox value="Option 1">
@for (option of options; track option) {
<div ngOption [value]="option">
<span class="example-option-text">{{ option }}</span>
<span
class="example-option-check material-symbols-outlined"
translate="no"
aria-hidden="true"
>check</span
>
</div>
}
</div>
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-family: var(--inter-font);
}
.listbox-container {
width: 200px;
height: 11rem;
padding: 0.5rem;
border-radius: 0.5rem;
background-color: var(--septenary-contrast);
font-size: 0.9rem;
}
[ngListbox] {
gap: 2px;
height: 100%;
display: flex;
overflow: auto;
flex-direction: column;
}
[ngOption] {
display: flex;
cursor: pointer;
align-items: center;
margin: 1px;
padding: 0 1rem;
min-height: 2.25rem;
border-radius: 0.5rem;
}
[ngOption]:hover {
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}
[ngOption][data-active='true'] {
outline-offset: -2px;
outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}
[ngOption][aria-selected='true'] {
color: var(--hot-pink);
background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}
[ngOption]:not([aria-selected='true']) .example-option-check {
display: none;
}
.example-option-check {
font-size: 0.9rem;
}
.example-option-text {
flex: 1;
}
import {Listbox, Option} from '@angular/aria/listbox';
import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [Listbox, Option],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
/** The options available in the listbox. */
options = [
'Option 1',
'Option 2',
'Option 3',
'Option 4',
'Option 5',
'Option 6',
'Option 7',
'Option 8',
];
}
<div class="material-listbox">
<div ngListbox>
@for (option of options; track option) {
<div ngOption [value]="option">
<span class="example-option-text">{{ option }}</span>
<span
class="example-option-check material-symbols-outlined"
translate="no"
aria-hidden="true"
>check</span
>
</div>
}
</div>
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
display: flex;
justify-content: center;
font-family: var(--inter-font);
--primary: var(--hot-pink);
--on-primary: var(--page-background);
}
.docs-light-mode {
--on-primary: #fff;
}
.material-listbox {
width: 200px;
height: 13rem;
padding: 0.5rem;
border-radius: 2rem;
background-color: var(--septenary-contrast);
font-size: 0.9rem;
}
[ngListbox] {
gap: 2px;
padding: 2px;
height: 100%;
display: flex;
overflow: auto;
flex-direction: column;
}
[ngOption] {
display: flex;
cursor: pointer;
align-items: center;
padding: 0 1rem;
min-height: 3rem;
border-radius: 3rem;
}
[ngOption]:hover,
[ngOption][data-active='true'] {
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}
[ngOption][data-active='true'] {
outline-offset: -2px;
outline: 2px solid var(--primary);
}
[ngOption][aria-selected='true'] {
color: var(--primary);
background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}
[ngOption]:not([aria-selected='true']) .example-option-check {
display: none;
}
.example-option-check {
font-size: 0.9rem;
}
.example-option-text {
flex: 1;
}
import {Listbox, Option} from '@angular/aria/listbox';
import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [Listbox, Option],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
/** The options available in the listbox. */
options = [
'Option 1',
'Option 2',
'Option 3',
'Option 4',
'Option 5',
'Option 6',
'Option 7',
'Option 8',
];
}
<div class="retro-listbox">
<div ngListbox>
@for (option of options; track option) {
<div ngOption [value]="option">
<span class="example-option-text">{{ option }}</span>
<span
class="example-option-check material-symbols-outlined"
translate="no"
aria-hidden="true"
>check</span
>
</div>
}
</div>
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
:host {
display: flex;
justify-content: center;
font-size: 0.8rem;
font-family: 'Press Start 2P';
--retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
--retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
--retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
}
.retro-listbox {
width: 200px;
height: 11rem;
padding: 0.5rem;
box-shadow: var(--retro-flat-shadow);
background-color: var(--septenary-contrast);
}
[ngListbox] {
gap: 2px;
height: 100%;
display: flex;
overflow: auto;
flex-direction: column;
}
[ngOption] {
display: flex;
cursor: pointer;
align-items: center;
padding: 0 1rem;
font-size: 0.6rem;
min-height: 2.25rem;
}
[ngOption]:hover {
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}
[ngOption][data-active='true'] {
outline-offset: -2px;
outline: 2px dashed var(--hot-pink);
}
[ngOption][aria-selected='true'] {
color: var(--hot-pink);
background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}
[ngOption]:not([aria-selected='true']) .example-option-check {
display: none;
}
.example-option-icon,
.example-option-check {
font-size: 0.9rem;
}
.example-option-text {
flex: 1;
}
Usage Listbox is a foundational directive used by the Select , Multiselect , and Autocomplete patterns. For most dropdown needs, use those documented patterns instead.
Consider using listbox directly when:
Building custom selection components - Creating specialized interfaces with specific behavior
Visible selection lists - Displaying selectable items directly on the page (not in dropdowns)
Custom integration patterns - Integrating with unique popup or layout requirements Avoid listbox when:
Navigation menus are needed - Use the Menu directive for actions and commands Features Angular's listbox provides a fully accessible list implementation with:
Keyboard Navigation - Navigate options with arrow keys, select with Enter or Space
Screen Reader Support - Built-in ARIA attributes including role="listbox"
Single or Multiple Selection - multi attribute controls selection mode
Horizontal or Vertical - orientation attribute for layout direction
Type-ahead Search - Type characters to jump to matching options
Signal-Based Reactivity - Reactive state management using Angular signals Examples Basic listbox Applications sometimes need selectable lists visible directly on the page rather than hidden in a dropdown. A standalone listbox provides keyboard navigation and selection for these visible list interfaces.
import {Listbox, Option} from '@angular/aria/listbox';
import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [Listbox, Option],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
/** The options available in the listbox. */
options = [
'Option 1',
'Option 2',
'Option 3',
'Option 4',
'Option 5',
'Option 6',
'Option 7',
'Option 8',
];
}
<div class="listbox-container">
<div ngListbox value="Option 1">
@for (option of options; track option) {
<div ngOption [value]="option">
<span class="example-option-text">{{ option }}</span>
<span
class="example-option-check material-symbols-outlined"
translate="no"
aria-hidden="true"
>check</span
>
</div>
}
</div>
</div>
The values model signal provides two-way binding to the selected items. With selectionMode="explicit", users press Space or Enter to select options. For dropdown patterns that combine listbox with combobox and overlay positioning, see the Select pattern.
Horizontal listbox Lists sometimes work better horizontally, such as toolbar-like interfaces or tab-style selections. The orientation attribute changes both the layout and keyboard navigation direction.
import {Listbox, Option} from '@angular/aria/listbox';
import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [Listbox, Option],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
/** The options available in the listbox. */
amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
<div ngListbox aria-label="Amenities" orientation="horizontal" selectionMode="explicit" multi>
@for (amenity of amenities; track amenity) {
<div ngOption [value]="amenity" [label]="amenity">
<span class="option-label">{{ amenity }}</span>
</div>
}
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
font-size: 0.8rem;
font-family: var(--inter-font);
}
[ngListbox] {
gap: 0.5rem;
display: flex;
flex-wrap: wrap;
}
[ngOption] {
cursor: pointer;
border-radius: 1rem;
padding: 0.3rem 1rem;
color: var(--hot-pink);
border: 1px solid var(--hot-pink);
background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}
[ngOption]:focus {
outline: 2px solid var(--hot-pink);
outline-offset: 2px;
}
[ngOption]:hover {
background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}
[ngOption][aria-selected='true'] {
color: var(--page-background);
background-color: var(--hot-pink);
}
import {Listbox, Option} from '@angular/aria/listbox';
import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [Listbox, Option],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
/** The options available in the listbox. */
amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
<div
ngListbox
class="material-listbox"
aria-label="Amenities"
orientation="horizontal"
selectionMode="explicit"
multi
>
@for (amenity of amenities; track amenity) {
<div ngOption [value]="amenity" [label]="amenity">
<span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="option-label">{{ amenity }}</span>
</div>
}
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
:host {
font-size: 0.8rem;
font-family: var(--inter-font);
}
[ngListbox] {
gap: 0.5rem;
display: flex;
flex-wrap: wrap;
}
[ngOption] {
display: flex;
cursor: pointer;
align-items: center;
border-radius: 0.3rem;
padding: 0.3rem 0.5rem;
color: var(--hot-pink);
border: 1px solid var(--hot-pink);
background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}
[ngOption]:focus {
outline: 2px solid var(--hot-pink);
outline-offset: 2px;
}
[ngOption]:hover {
background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}
[ngOption][aria-selected='true'] {
color: var(--page-background);
background-color: var(--hot-pink);
}
.check-icon {
width: 0;
font-size: 1.25rem;
overflow: hidden;
transition:
width 0.2s ease-in-out,
padding-right 0.2s ease-in-out;
}
[ngOption][aria-selected='true'] .check-icon {
width: 1.5rem;
padding-right: 0.2rem;
}
import {Listbox, Option} from '@angular/aria/listbox';
import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [Listbox, Option],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
/** The options available in the listbox. */
amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
<div
ngListbox
class="retro-listbox"
aria-label="Amenities"
orientation="horizontal"
selectionMode="explicit"
multi
>
@for (amenity of amenities; track amenity) {
<div ngOption [value]="amenity" [label]="amenity">
<span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
>check</span
>
<span class="option-label">{{ amenity }}</span>
</div>
}
</div>
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
:host {
display: flex;
justify-content: center;
font-size: 0.8rem;
font-family: 'Press Start 2P';
--retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
--retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
--retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
--retro-flat-shadow:
4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
-4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
--retro-flat-shadow-small:
2px 0px 0px 0px var(--tertiary-contrast), 0px 2px 0px 0px var(--tertiary-contrast),
-2px 0px 0px 0px var(--tertiary-contrast), 0px -2px 0px 0px var(--tertiary-contrast);
}
.retro-listbox {
gap: 0.5rem;
display: flex;
flex-wrap: wrap;
padding: 0.5rem;
box-shadow: var(--retro-flat-shadow);
background-color: var(--septenary-contrast);
}
[ngOption] {
display: flex;
cursor: pointer;
align-items: center;
padding: 0.3rem 0.5rem;
font-size: 0.6rem;
box-shadow: var(--retro-flat-shadow-small);
}
[ngOption]:hover {
background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}
[ngOption][data-active='true'] {
outline-offset: -2px;
outline: 2px dashed var(--hot-pink);
}
[ngOption][aria-selected='true'] {
color: var(--hot-pink);
background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}
.check-icon {
width: 0;
font-size: 0.9rem;
overflow: hidden;
transition: width 0.2s ease-in-out;
}
[ngOption][aria-selected='true'] .check-icon {
width: 1.2rem;
}
With orientation="horizontal", left and right arrow keys navigate between options instead of up and down. The listbox automatically handles right-to-left (RTL) languages by reversing navigation direction.
Selection modes Listbox supports two selection modes that control when items become selected.
The 'follow' mode automatically selects the focused item, providing faster interaction when selection changes frequently. The 'explicit' mode requires Space or Enter to confirm selection, preventing accidental changes while navigating. Dropdown patterns typically use 'follow' mode for single selection.
Explicit import {Component} from '@angular/core';
import {Listbox, Option} from '@angular/aria/listbox';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [Listbox, Option],
})
export class App {
amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
<div
ngListbox
aria-label="Amenities_explicit"
orientation="horizontal"
selectionMode="explicit"
multi
>
@for (amenity of amenities; track amenity) {
<div ngOption [value]="amenity" [label]="amenity">
<span class="option-label">{{ amenity }}</span>
</div>
}
</div>
Follow import {Component} from '@angular/core';
import {Listbox, Option} from '@angular/aria/listbox';
@Component({
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
imports: [Listbox, Option],
})
export class App {
amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
<div
ngListbox
aria-label="Amenities_explicit"
orientation="horizontal"
selectionMode="follow"
multi
>
@for (amenity of amenities; track amenity) {
<div ngOption [value]="amenity" [label]="amenity">
<span class="option-label">{{ amenity }}</span>
</div>
}
</div>
TIP: Dropdown patterns typically use 'follow' mode for single selection.
APIs Listbox Directive The ngListbox directive creates an accessible list of selectable options.
Model Signals Methods Option Directive The ngOption directive marks an item within a listbox.
Signals Listbox is used by these documented dropdown patterns:
Select - Single-selection dropdown pattern using readonly combobox + listbox
Multiselect - Multiple-selection dropdown pattern using readonly combobox + listbox with multi
Autocomplete - Filterable dropdown pattern using combobox + listbox For complete dropdown patterns with trigger, popup, and overlay positioning, see those pattern guides instead of using listbox alone.
Listbox ARIA pattern Listbox API Reference