arrow_upward_alt Back to the top
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" >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 : 200 px ; height : 11 rem ; padding : 0.5 rem ; border-radius : 0.5 rem ; background-color : var ( --septenary-contrast ); font-size : 0.9 rem ; } [ ngListbox ] { gap : 2 px ; height : 100 % ; display : flex ; overflow : auto ; flex-direction : column ; } [ ngOption ] { display : flex ; cursor : pointer ; align-items : center ; margin : 1 px ; padding : 0 1 rem ; min-height : 2.25 rem ; border-radius : 0.5 rem ; } [ ngOption ] :hover { background-color : color-mix ( in srgb , var ( --primary-contrast ) 5 % , transparent ); } [ ngOption ][ data-active = 'true' ] { outline-offset : -2 px ; outline : 2 px 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.9 rem ; } .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" >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 : 200 px ; height : 13 rem ; padding : 0.5 rem ; border-radius : 2 rem ; background-color : var ( --septenary-contrast ); font-size : 0.9 rem ; } [ ngListbox ] { gap : 2 px ; padding : 2 px ; height : 100 % ; display : flex ; overflow : auto ; flex-direction : column ; } [ ngOption ] { display : flex ; cursor : pointer ; align-items : center ; padding : 0 1 rem ; min-height : 3 rem ; border-radius : 3 rem ; } [ ngOption ] :hover , [ ngOption ][ data-active = 'true' ] { background-color : color-mix ( in srgb , var ( --primary-contrast ) 5 % , transparent ); } [ ngOption ][ data-active = 'true' ] { outline-offset : -2 px ; outline : 2 px 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.9 rem ; } .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" >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.8 rem ; font-family : 'Press Start 2P' ; --retro-button-color : color-mix ( in srgb , var ( --hot-pink ) 80 % , var ( --gray-1000 )); --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 : 4 px 0 px 0 px 0 px var ( --gray-700 ), 0 px 4 px 0 px 0 px var ( --gray-700 ), -4 px 0 px 0 px 0 px var ( --gray-700 ), 0 px -4 px 0 px 0 px var ( --gray-700 ); } .retro-listbox { width : 200 px ; height : 11 rem ; padding : 0.5 rem ; box-shadow : var ( --retro-flat-shadow ); background-color : var ( --septenary-contrast ); } [ ngListbox ] { gap : 2 px ; height : 100 % ; display : flex ; overflow : auto ; flex-direction : column ; } [ ngOption ] { display : flex ; cursor : pointer ; align-items : center ; padding : 0 1 rem ; font-size : 0.6 rem ; min-height : 2.25 rem ; } [ ngOption ] :hover { background-color : color-mix ( in srgb , var ( --primary-contrast ) 5 % , transparent ); } [ ngOption ][ data-active = 'true' ] { outline-offset : -2 px ; outline : 2 px 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.9 rem ; } .example-option-text { flex : 1 ; }
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
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
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" >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.
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.8 rem ; font-family : var ( --inter-font ); } [ ngListbox ] { gap : 0.5 rem ; display : flex ; flex-wrap : wrap ; } [ ngOption ] { cursor : pointer ; border-radius : 1 rem ; padding : 0.3 rem 1 rem ; color : var ( --hot-pink ); border : 1 px solid var ( --hot-pink ); background-color : color-mix ( in srgb , var ( --hot-pink ) 5 % , transparent ); } [ ngOption ] :focus { outline : 2 px solid var ( --hot-pink ); outline-offset : 2 px ; } [ 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" >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.8 rem ; font-family : var ( --inter-font ); } [ ngListbox ] { gap : 0.5 rem ; display : flex ; flex-wrap : wrap ; } [ ngOption ] { display : flex ; cursor : pointer ; align-items : center ; border-radius : 0.3 rem ; padding : 0.3 rem 0.5 rem ; color : var ( --hot-pink ); border : 1 px solid var ( --hot-pink ); background-color : color-mix ( in srgb , var ( --hot-pink ) 5 % , transparent ); } [ ngOption ] :focus { outline : 2 px solid var ( --hot-pink ); outline-offset : 2 px ; } [ 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.25 rem ; overflow : hidden ; transition : width 0.2 s ease-in-out , padding-right 0.2 s ease-in-out ; } [ ngOption ][ aria-selected = 'true' ] .check-icon { width : 1.5 rem ; padding-right : 0.2 rem ; }
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" >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.8 rem ; font-family : 'Press Start 2P' ; --retro-button-color : color-mix ( in srgb , var ( --hot-pink ) 80 % , var ( --gray-1000 )); --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 : 4 px 0 px 0 px 0 px var ( --gray-700 ), 0 px 4 px 0 px 0 px var ( --gray-700 ), -4 px 0 px 0 px 0 px var ( --gray-700 ), 0 px -4 px 0 px 0 px var ( --gray-700 ); --retro-flat-shadow-small : 2 px 0 px 0 px 0 px var ( --gray-700 ), 0 px 2 px 0 px 0 px var ( --gray-700 ), -2 px 0 px 0 px 0 px var ( --gray-700 ), 0 px -2 px 0 px 0 px var ( --gray-700 ); } .retro-listbox { gap : 0.5 rem ; display : flex ; flex-wrap : wrap ; padding : 0.5 rem ; box-shadow : var ( --retro-flat-shadow ); background-color : var ( --septenary-contrast ); } [ ngOption ] { display : flex ; cursor : pointer ; align-items : center ; padding : 0.3 rem 0.5 rem ; font-size : 0.6 rem ; 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 : -2 px ; outline : 2 px 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.9 rem ; overflow : hidden ; transition : width 0.2 s ease-in-out ; } [ ngOption ][ aria-selected = 'true' ] .check-icon { width : 1.2 rem ; }
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.
Listbox supports two selection modes that control when items become selected. Choose the mode that matches your interface's interaction pattern.
import {Component} from '@angular/core' ; @ Component ({ selector: 'app-root' , templateUrl: './app.html' , styleUrl: './app.css' , }) export class App {}
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.
The ngListbox directive creates an accessible list of selectable options.
The ngOption directive marks an item within a listbox.
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