Skip to content

Commit 9b1f49f

Browse files
authored
fix(material/chips): add support for aria-description (#25935)
Add support for aria-description on mat-chip, mat-chip-option and mat-chip-row. mat-chip-option and mat-chip-row put aria-desciprtion on the same element that already has the aria-label. aria-description is needed for when developers need to provide more information that in the aria-label. This is especially needed for chip-row when [editable]="true". That's because it gives a way to communicate to screen reader users how to begin editing a chip.
1 parent c08e3f1 commit 9b1f49f

File tree

10 files changed

+58
-7
lines changed

10 files changed

+58
-7
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
<mat-chip-row *ngFor="let fruit of fruits"
55
(removed)="remove(fruit)"
66
[editable]="true"
7-
(edited)="edit(fruit, $event)">
7+
(edited)="edit(fruit, $event)"
8+
[aria-description]="'press enter to edit ' + fruit.name">
89
{{fruit.name}}
9-
<button matChipRemove [attr.aria-label]="'remove ' + fruit">
10+
<button matChipRemove [attr.aria-label]="'remove ' + fruit.name">
1011
<mat-icon>cancel</mat-icon>
1112
</button>
1213
</mat-chip-row>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class ChipsInputExample {
5050

5151
// Edit existing fruit
5252
const index = this.fruits.indexOf(fruit);
53-
if (index > 0) {
53+
if (index >= 0) {
5454
this.fruits[index].name = value;
5555
}
5656
}

src/material/chips/chip-option.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
[_allowFocusWhenDisabled]="true"
1212
[attr.aria-selected]="ariaSelected"
1313
[attr.aria-label]="ariaLabel"
14+
[attr.aria-description]="ariaDescription"
1415
role="option">
1516
<span class="mdc-evolution-chip__graphic mat-mdc-chip-graphic">
1617
<ng-content select="mat-chip-avatar, [matChipAvatar]"></ng-content>

src/material/chips/chip-option.spec.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,23 @@ describe('MDC-based Option Chips', () => {
296296
});
297297
});
298298

299+
describe('a11y', () => {
300+
it('should apply `ariaLabel` and `ariaDesciption` to the element with option role', () => {
301+
testComponent.ariaLabel = 'option name';
302+
testComponent.ariaDescription = 'option description';
303+
304+
fixture.detectChanges();
305+
306+
const optionElement = fixture.nativeElement.querySelector('[role="option"]') as HTMLElement;
307+
expect(optionElement)
308+
.withContext('expected to find an element with option role')
309+
.toBeTruthy();
310+
311+
expect(optionElement.getAttribute('aria-label')).toBe('option name');
312+
expect(optionElement.getAttribute('aria-description')).toBe('option description');
313+
});
314+
});
315+
299316
it('should contain a focus indicator inside the text label', () => {
300317
const label = chipNativeElement.querySelector('.mdc-evolution-chip__text-label');
301318
expect(label?.querySelector('.mat-mdc-focus-indicator')).toBeTruthy();
@@ -310,7 +327,8 @@ describe('MDC-based Option Chips', () => {
310327
<mat-chip-option [selectable]="selectable"
311328
[color]="color" [selected]="selected" [disabled]="disabled"
312329
(destroyed)="chipDestroy($event)"
313-
(selectionChange)="chipSelectionChange($event)">
330+
(selectionChange)="chipSelectionChange($event)"
331+
[aria-label]="ariaLabel" [aria-description]="ariaDescription">
314332
<span class="avatar" matChipAvatar></span>
315333
{{name}}
316334
</mat-chip-option>
@@ -325,6 +343,8 @@ class SingleChip {
325343
selected: boolean = false;
326344
selectable: boolean = true;
327345
shouldShow: boolean = true;
346+
ariaLabel: string | null = null;
347+
ariaDescription: string | null = null;
328348

329349
chipDestroy: (event?: MatChipEvent) => void = () => {};
330350
chipSelectionChange: (event?: MatChipSelectionChange) => void = () => {};

src/material/chips/chip-option.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export class MatChipSelectionChange {
6363
'[class.mat-mdc-chip-with-trailing-icon]': '_hasTrailingIcon()',
6464
'[attr.tabindex]': 'null',
6565
'[attr.aria-label]': 'null',
66+
'[attr.aria-description]': 'null',
6667
'[attr.role]': 'role',
6768
'[id]': 'id',
6869
},

src/material/chips/chip-row.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
[attr.role]="editable ? 'button' : null"
1414
[tabIndex]="tabIndex"
1515
[disabled]="disabled"
16-
[attr.aria-label]="ariaLabel">
16+
[attr.aria-label]="ariaLabel"
17+
[attr.aria-description]="ariaDescription">
1718
<span class="mdc-evolution-chip__graphic mat-mdc-chip-graphic" *ngIf="leadingIcon">
1819
<ng-content select="mat-chip-avatar, [matChipAvatar]"></ng-content>
1920
</span>

src/material/chips/chip-row.spec.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,25 @@ describe('MDC-based Row Chips', () => {
332332
expect(document.activeElement).not.toBe(primaryAction);
333333
}));
334334
});
335+
336+
describe('a11y', () => {
337+
it('should apply `ariaLabel` and `ariaDesciption` to the primary gridcell', () => {
338+
fixture.componentInstance.ariaLabel = 'chip name';
339+
fixture.componentInstance.ariaDescription = 'chip description';
340+
341+
fixture.detectChanges();
342+
343+
const primaryGridCell = fixture.nativeElement.querySelector(
344+
'[role="gridcell"].mdc-evolution-chip__cell--primary .mat-mdc-chip-action',
345+
);
346+
expect(primaryGridCell)
347+
.withContext('expected to find the grid cell for the primary chip action')
348+
.toBeTruthy();
349+
350+
expect(primaryGridCell.getAttribute('aria-label')).toBe('chip name');
351+
expect(primaryGridCell.getAttribute('aria-description')).toBe('chip description');
352+
});
353+
});
335354
});
336355
});
337356

@@ -342,7 +361,8 @@ describe('MDC-based Row Chips', () => {
342361
<mat-chip-row [removable]="removable"
343362
[color]="color" [disabled]="disabled" [editable]="editable"
344363
(destroyed)="chipDestroy($event)"
345-
(removed)="chipRemove($event)" (edited)="chipEdit($event)">
364+
(removed)="chipRemove($event)" (edited)="chipEdit($event)"
365+
[aria-label]="ariaLabel" [aria-description]="ariaDescription">
346366
{{name}}
347367
<button matChipRemove>x</button>
348368
<span *ngIf="useCustomEditInput" class="projected-edit-input" matChipEditInput></span>
@@ -361,6 +381,8 @@ class SingleChip {
361381
shouldShow: boolean = true;
362382
editable: boolean = false;
363383
useCustomEditInput: boolean = true;
384+
ariaLabel: string | null = null;
385+
ariaDescription: string | null = null;
364386

365387
chipDestroy: (event?: MatChipEvent) => void = () => {};
366388
chipRemove: (event?: MatChipEvent) => void = () => {};

src/material/chips/chip-row.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export interface MatChipEditedEvent extends MatChipEvent {
6464
'[id]': 'id',
6565
'[attr.tabindex]': 'null',
6666
'[attr.aria-label]': 'null',
67+
'[attr.aria-description]': 'null',
6768
'[attr.role]': 'role',
6869
'(mousedown)': '_mousedown($event)',
6970
'(dblclick)': '_doubleclick($event)',

src/material/chips/chip.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ export class MatChip
150150
/** ARIA label for the content of the chip. */
151151
@Input('aria-label') ariaLabel: string | null = null;
152152

153+
/** ARIA description for the content of the chip. */
154+
@Input('aria-description') ariaDescription: string | null = null;
155+
153156
private _textElement!: HTMLElement;
154157

155158
/**

tools/public_api_guard/material/chips.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export const MAT_CHIPS_DEFAULT_OPTIONS: InjectionToken<MatChipsDefaultOptions>;
6464
export class MatChip extends _MatChipMixinBase implements AfterViewInit, CanColor, CanDisableRipple, CanDisable, HasTabIndex, OnDestroy {
6565
constructor(_changeDetectorRef: ChangeDetectorRef, elementRef: ElementRef<HTMLElement>, _ngZone: NgZone, _focusMonitor: FocusMonitor, _document: any, animationMode?: string, _globalRippleOptions?: RippleGlobalOptions | undefined, tabIndex?: string);
6666
_animationsDisabled: boolean;
67+
ariaDescription: string | null;
6768
ariaLabel: string | null;
6869
protected basicChipAttrName: string;
6970
// (undocumented)
@@ -112,7 +113,7 @@ export class MatChip extends _MatChipMixinBase implements AfterViewInit, CanColo
112113
// (undocumented)
113114
protected _value: any;
114115
// (undocumented)
115-
static ɵcmp: i0.ɵɵComponentDeclaration<MatChip, "mat-basic-chip, mat-chip", ["matChip"], { "color": "color"; "disabled": "disabled"; "disableRipple": "disableRipple"; "tabIndex": "tabIndex"; "role": "role"; "id": "id"; "ariaLabel": "aria-label"; "value": "value"; "removable": "removable"; "highlighted": "highlighted"; }, { "removed": "removed"; "destroyed": "destroyed"; }, ["leadingIcon", "trailingIcon", "removeIcon"], ["mat-chip-avatar, [matChipAvatar]", "*", "mat-chip-trailing-icon,[matChipRemove],[matChipTrailingIcon]"], false, never>;
116+
static ɵcmp: i0.ɵɵComponentDeclaration<MatChip, "mat-basic-chip, mat-chip", ["matChip"], { "color": "color"; "disabled": "disabled"; "disableRipple": "disableRipple"; "tabIndex": "tabIndex"; "role": "role"; "id": "id"; "ariaLabel": "aria-label"; "ariaDescription": "aria-description"; "value": "value"; "removable": "removable"; "highlighted": "highlighted"; }, { "removed": "removed"; "destroyed": "destroyed"; }, ["leadingIcon", "trailingIcon", "removeIcon"], ["mat-chip-avatar, [matChipAvatar]", "*", "mat-chip-trailing-icon,[matChipRemove],[matChipTrailingIcon]"], false, never>;
116117
// (undocumented)
117118
static ɵfac: i0.ɵɵFactoryDeclaration<MatChip, [null, null, null, null, null, { optional: true; }, { optional: true; }, { attribute: "tabindex"; }]>;
118119
}

0 commit comments

Comments
 (0)