Skip to content

Commit cac1cbb

Browse files
committed
docs(material/chips): require announcing removal of chip to AT (#27197)
Update the accessibility instructions for the `MatChipRemove` directive. Require author to communicate to Assisstive Technology when a chip is removed. This can be done by sending a message with `LiveAnnouncer`. Following these instructions avoids an a11y issue where removing a chip is communicated only visually, and it is not communicated with any other senses (#12122). Update all examples to follow these instructions by announcing deletion with `LiveAnnouncer`. Fix #12122 (cherry picked from commit cddb04f)
1 parent bb6f61b commit cac1cbb

File tree

5 files changed

+26
-5
lines changed

5 files changed

+26
-5
lines changed

src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {COMMA, ENTER} from '@angular/cdk/keycodes';
2-
import {Component, ElementRef, ViewChild} from '@angular/core';
2+
import {Component, ElementRef, ViewChild, inject} from '@angular/core';
33
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
44
import {MatAutocompleteSelectedEvent, MatAutocompleteModule} from '@angular/material/autocomplete';
55
import {MatChipInputEvent, MatChipsModule} from '@angular/material/chips';
@@ -8,6 +8,7 @@ import {map, startWith} from 'rxjs/operators';
88
import {MatIconModule} from '@angular/material/icon';
99
import {NgFor, AsyncPipe} from '@angular/common';
1010
import {MatFormFieldModule} from '@angular/material/form-field';
11+
import {LiveAnnouncer} from '@angular/cdk/a11y';
1112

1213
/**
1314
* @title Chips Autocomplete
@@ -37,6 +38,8 @@ export class ChipsAutocompleteExample {
3738

3839
@ViewChild('fruitInput') fruitInput: ElementRef<HTMLInputElement>;
3940

41+
announcer = inject(LiveAnnouncer);
42+
4043
constructor() {
4144
this.filteredFruits = this.fruitCtrl.valueChanges.pipe(
4245
startWith(null),
@@ -63,6 +66,8 @@ export class ChipsAutocompleteExample {
6366

6467
if (index >= 0) {
6568
this.fruits.splice(index, 1);
69+
70+
this.announcer.announce(`Removed ${fruit}`);
6671
}
6772
}
6873

src/components-examples/material/chips/chips-form-control/chips-form-control-example.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import {Component} from '@angular/core';
1+
import {Component, inject} from '@angular/core';
22
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
33
import {MatChipInputEvent, MatChipsModule} from '@angular/material/chips';
44
import {MatIconModule} from '@angular/material/icon';
55
import {NgFor} from '@angular/common';
66
import {MatFormFieldModule} from '@angular/material/form-field';
77
import {MatButtonModule} from '@angular/material/button';
8+
import {LiveAnnouncer} from '@angular/cdk/a11y';
89

910
/**
1011
* @title Chips with form control
@@ -28,10 +29,14 @@ export class ChipsFormControlExample {
2829
keywords = ['angular', 'how-to', 'tutorial', 'accessibility'];
2930
formControl = new FormControl(['angular']);
3031

32+
announcer = inject(LiveAnnouncer);
33+
3134
removeKeyword(keyword: string) {
3235
const index = this.keywords.indexOf(keyword);
3336
if (index >= 0) {
3437
this.keywords.splice(index, 1);
38+
39+
this.announcer.announce(`removed ${keyword}`);
3540
}
3641
}
3742

src/components-examples/material/chips/chips-input/chips-input-example.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import {COMMA, ENTER} from '@angular/cdk/keycodes';
2-
import {Component} from '@angular/core';
2+
import {Component, inject} from '@angular/core';
33
import {MatChipEditedEvent, MatChipInputEvent, MatChipsModule} from '@angular/material/chips';
44
import {MatIconModule} from '@angular/material/icon';
55
import {NgFor} from '@angular/common';
66
import {MatFormFieldModule} from '@angular/material/form-field';
7+
import {LiveAnnouncer} from '@angular/cdk/a11y';
78

89
export interface Fruit {
910
name: string;
@@ -24,6 +25,8 @@ export class ChipsInputExample {
2425
readonly separatorKeysCodes = [ENTER, COMMA] as const;
2526
fruits: Fruit[] = [{name: 'Lemon'}, {name: 'Lime'}, {name: 'Apple'}];
2627

28+
announcer = inject(LiveAnnouncer);
29+
2730
add(event: MatChipInputEvent): void {
2831
const value = (event.value || '').trim();
2932

@@ -41,6 +44,8 @@ export class ChipsInputExample {
4144

4245
if (index >= 0) {
4346
this.fruits.splice(index, 1);
47+
48+
this.announcer.announce(`Removed ${fruit}`);
4449
}
4550
}
4651

src/dev-app/chips/chips-demo.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Component} from '@angular/core';
9+
import {Component, inject} from '@angular/core';
1010
import {COMMA, ENTER} from '@angular/cdk/keycodes';
1111
import {CommonModule} from '@angular/common';
1212
import {ThemePalette} from '@angular/material/core';
@@ -18,6 +18,7 @@ import {MatCheckboxModule} from '@angular/material/checkbox';
1818
import {MatFormFieldModule} from '@angular/material/form-field';
1919
import {MatToolbarModule} from '@angular/material/toolbar';
2020
import {MatIconModule} from '@angular/material/icon';
21+
import {LiveAnnouncer} from '@angular/cdk/a11y';
2122

2223
export interface Person {
2324
name: string;
@@ -92,6 +93,8 @@ export class ChipsDemo {
9293
{name: 'Warn', color: 'warn'},
9394
];
9495

96+
announcer = inject(LiveAnnouncer);
97+
9598
displayMessage(message: string): void {
9699
this.message = message;
97100
}
@@ -113,6 +116,7 @@ export class ChipsDemo {
113116

114117
if (index >= 0) {
115118
this.people.splice(index, 1);
119+
this.announcer.announce(`Removed ${person.name}`);
116120
}
117121
}
118122

src/material/chips/chips.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ To create a remove button, nest a `<button>` element with `matChipRemove` attrib
9090
</mat-chip-option>
9191
```
9292

93-
See the [accessibility](#accessibility) section for how to create accessible icons.
93+
See the [accessibility](#accessibility) section for best practices on implementing the `removed` Output and creating accessible icons.
9494

9595
### Orientation
9696

@@ -186,6 +186,8 @@ For both MatChipGrid and MatChipListbox, always apply an accessible label to the
186186

187187
Always apply MatChipRemove to a `<button>` element, never a `<mat-icon>` element.
188188

189+
When using `MatChipRemove`, you should always communicate removals for assistive technology. One way to accomplish this is by sending a message with `LiveAnnouncer`. Otherwise, removing a chip may only be communicated visually.
190+
189191
When using MatChipListbox, never nest other interactive controls inside of the `<mat-chip-option>` element. Nesting controls degrades the experience for assistive technology users.
190192

191193
By default, `MatChipListbox` displays a checkmark to identify selected items. While you can hide the checkmark indicator for single-selection via `hideSingleSelectionIndicator`, this makes the component less accessible by making it harder or impossible for users to visually identify selected items.

0 commit comments

Comments
 (0)