Skip to content

Commit

Permalink
docs(material/chips): require announcing removal of chip to AT (angul…
Browse files Browse the repository at this point in the history
…ar#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 (angular#12122).

Update all examples to follow these instructions by announcing deletion
with `LiveAnnouncer`.

Fix angular#12122
  • Loading branch information
zarend authored Jun 9, 2023
1 parent fd9591b commit cddb04f
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {COMMA, ENTER} from '@angular/cdk/keycodes';
import {Component, ElementRef, ViewChild} from '@angular/core';
import {Component, ElementRef, ViewChild, inject} from '@angular/core';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatAutocompleteSelectedEvent, MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatChipInputEvent, MatChipsModule} from '@angular/material/chips';
Expand All @@ -8,6 +8,7 @@ import {map, startWith} from 'rxjs/operators';
import {MatIconModule} from '@angular/material/icon';
import {NgFor, AsyncPipe} from '@angular/common';
import {MatFormFieldModule} from '@angular/material/form-field';
import {LiveAnnouncer} from '@angular/cdk/a11y';

/**
* @title Chips Autocomplete
Expand Down Expand Up @@ -37,6 +38,8 @@ export class ChipsAutocompleteExample {

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

announcer = inject(LiveAnnouncer);

constructor() {
this.filteredFruits = this.fruitCtrl.valueChanges.pipe(
startWith(null),
Expand All @@ -63,6 +66,8 @@ export class ChipsAutocompleteExample {

if (index >= 0) {
this.fruits.splice(index, 1);

this.announcer.announce(`Removed ${fruit}`);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {Component} from '@angular/core';
import {Component, inject} from '@angular/core';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatChipInputEvent, MatChipsModule} from '@angular/material/chips';
import {MatIconModule} from '@angular/material/icon';
import {NgFor} from '@angular/common';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatButtonModule} from '@angular/material/button';
import {LiveAnnouncer} from '@angular/cdk/a11y';

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

announcer = inject(LiveAnnouncer);

removeKeyword(keyword: string) {
const index = this.keywords.indexOf(keyword);
if (index >= 0) {
this.keywords.splice(index, 1);

this.announcer.announce(`removed ${keyword}`);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {COMMA, ENTER} from '@angular/cdk/keycodes';
import {Component} from '@angular/core';
import {Component, inject} from '@angular/core';
import {MatChipEditedEvent, MatChipInputEvent, MatChipsModule} from '@angular/material/chips';
import {MatIconModule} from '@angular/material/icon';
import {NgFor} from '@angular/common';
import {MatFormFieldModule} from '@angular/material/form-field';
import {LiveAnnouncer} from '@angular/cdk/a11y';

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

announcer = inject(LiveAnnouncer);

add(event: MatChipInputEvent): void {
const value = (event.value || '').trim();

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

if (index >= 0) {
this.fruits.splice(index, 1);

this.announcer.announce(`Removed ${fruit}`);
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/dev-app/chips/chips-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Component} from '@angular/core';
import {Component, inject} from '@angular/core';
import {COMMA, ENTER} from '@angular/cdk/keycodes';
import {CommonModule} from '@angular/common';
import {ThemePalette} from '@angular/material/core';
Expand All @@ -18,6 +18,7 @@ import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatToolbarModule} from '@angular/material/toolbar';
import {MatIconModule} from '@angular/material/icon';
import {LiveAnnouncer} from '@angular/cdk/a11y';

export interface Person {
name: string;
Expand Down Expand Up @@ -92,6 +93,8 @@ export class ChipsDemo {
{name: 'Warn', color: 'warn'},
];

announcer = inject(LiveAnnouncer);

displayMessage(message: string): void {
this.message = message;
}
Expand All @@ -113,6 +116,7 @@ export class ChipsDemo {

if (index >= 0) {
this.people.splice(index, 1);
this.announcer.announce(`Removed ${person.name}`);
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/material/chips/chips.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ To create a remove button, nest a `<button>` element with `matChipRemove` attrib
</mat-chip-option>
```

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

### Orientation

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

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

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.

When using MatChipListbox, never nest other interactive controls inside of the `<mat-chip-option>` element. Nesting controls degrades the experience for assistive technology users.

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 comments on commit cddb04f

Please sign in to comment.