ユーザーが選択するためのオプションのリストを表示するディレクティブで、キーボードナビゲーション、単一または複数選択、スクリーンリーダーをサポートしています。
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;
}
Listboxは、Select、Multiselect、Autocompleteの各パターンで使用される基本的なディレクティブです。ほとんどのドロップダウンのニーズには、代わりにこれらのドキュメント化されたパターンを使用してください。
次のような場合は、listboxを直接使用することを検討してください:
- カスタム選択コンポーネントの構築 - 特定の動作を持つ特殊なインターフェースを作成する
- 可視の選択リスト - 選択可能な項目を(ドロップダウンではなく)ページに直接表示する
- カスタム統合パターン - 独自のポップアップやレイアウト要件と統合する
次のような場合は、listboxの使用を避けてください:
- ナビゲーションメニューが必要な場合 - アクションやコマンドにはMenuディレクティブを使用してください
Angularのリストボックスは、以下の機能を備えた完全にアクセシブルなリスト実装を提供します:
- キーボードナビゲーション - 矢印キーでオプションを移動し、EnterキーまたはSpaceキーで選択
- スクリーンリーダーのサポート -
role="listbox"を含む組み込みのARIA属性
- 単一選択または複数選択 -
multi属性で選択モードを制御
- 水平または垂直 -
orientation属性でレイアウト方向を指定
- 先行入力による検索 - 文字を入力して一致するオプションにジャンプ
- シグナルベースのリアクティビティ - Angularシグナルを使用したリアクティブな状態管理
アプリケーションでは、ドロップダウンに隠すのではなく、ページ上に直接表示される選択可能なリストが必要になることがあります。スタンドアロンのリストボックスは、これらの表示されるリストインターフェースに対して、キーボードによるナビゲーションと選択機能を提供します。
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>
valuesモデルシグナルは、選択されたアイテムへの双方向バインディングを提供します。selectionMode="explicit"では、ユーザーはSpaceキーまたはEnterキーを押してオプションを選択します。リストボックスとコンボボックス、オーバーレイ配置を組み合わせたドロップダウンパターンについては、Selectパターンを参照してください。
ツールバーのようなインターフェースやタブ形式の選択など、リストは水平方向に配置した方がうまく機能する場合があります。orientation属性は、レイアウトとキーボードナビゲーションの方向の両方を変更します。
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;
}
orientation="horizontal"の場合、上下の矢印キーの代わりに、左右の矢印キーでオプション間を移動します。リストボックスは、ナビゲーションの方向を反転させることで、右から左へ記述する言語 (RTL) に自動的に対応します。
リストボックスは、アイテムがいつ選択されるかを制御する2つの選択モードをサポートしています。
'follow'モードはフォーカスされたアイテムを自動的に選択し、選択が頻繁に変わる場合により速いインタラクションを提供します。'explicit'モードは選択を確定するためにSpaceキーまたはEnterキーが必要で、ナビゲーション中の意図しない変更を防ぎます。ドロップダウンパターンでは、通常、単一選択のために'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="explicit"
multi
>
@for (amenity of amenities; track amenity) {
<div ngOption [value]="amenity" [label]="amenity">
<span class="option-label">{{ amenity }}</span>
</div>
}
</div>
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: ドロップダウンパターンでは、通常、単一選択のために'follow'モードが使用されます。
ngListboxディレクティブは、選択可能なオプションのアクセシブルなリストを作成します。
ngOptionディレクティブは、リストボックス内のアイテムをマークします。
Listboxは、これらのドキュメント化されたドロップダウンパターンで使用されます:
- Select - 読み取り専用のcombobox + listboxを使用した単一選択のドロップダウンパターン
- Multiselect -
multiを使用した読み取り専用のcombobox + listboxによる複数選択のドロップダウンパターン
- Autocomplete - combobox + listboxを使用したフィルタリング可能なドロップダウンパターン
トリガー、ポップアップ、オーバーレイの配置を含む完全なドロップダウンパターンについては、listboxを単独で使用するのではなく、それらのパターンガイドを参照してください。