Skip to content

Commit dcf2fac

Browse files
authored
fix(material/chips): implement ariaDescription with aria-describedby (#26105)
For the `ariaDescription` Input, implement with aria-describedby rather than aria-description. aria-description is still in W3C Editor's Draft for ARIA 1.3.
1 parent 03264d8 commit dcf2fac

File tree

6 files changed

+61
-7
lines changed

6 files changed

+61
-7
lines changed

src/material/chips/chip-option.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
[_allowFocusWhenDisabled]="true"
1212
[attr.aria-selected]="ariaSelected"
1313
[attr.aria-label]="ariaLabel"
14-
[attr.aria-description]="ariaDescription"
14+
[attr.aria-describedby]="_ariaDescriptionId"
1515
role="option">
1616
<span class="mdc-evolution-chip__graphic mat-mdc-chip-graphic">
1717
<ng-content select="mat-chip-avatar, [matChipAvatar]"></ng-content>
@@ -34,3 +34,5 @@
3434
*ngIf="_hasTrailingIcon()">
3535
<ng-content select="mat-chip-trailing-icon,[matChipRemove],[matChipTrailingIcon]"></ng-content>
3636
</span>
37+
38+
<span class="cdk-visually-hidden" [id]="_ariaDescriptionId">{{ariaDescription}}</span>

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,28 @@ describe('MDC-based Option Chips', () => {
308308
.withContext('expected to find an element with option role')
309309
.toBeTruthy();
310310

311-
expect(optionElement.getAttribute('aria-label')).toBe('option name');
312-
expect(optionElement.getAttribute('aria-description')).toBe('option description');
311+
expect(optionElement.getAttribute('aria-label')).toMatch(/option name/i);
312+
313+
const optionElementDescribedBy = optionElement!.getAttribute('aria-describedby');
314+
expect(optionElementDescribedBy)
315+
.withContext('expected primary grid cell to have a non-empty aria-describedby attribute')
316+
.toBeTruthy();
317+
318+
const optionElementDescriptions = Array.from(
319+
(fixture.nativeElement as HTMLElement).querySelectorAll(
320+
optionElementDescribedBy!
321+
.split(/\s+/g)
322+
.map(x => `#${x}`)
323+
.join(','),
324+
),
325+
);
326+
327+
const optionElementDescription = optionElementDescriptions
328+
.map(x => x.textContent?.trim())
329+
.join(' ')
330+
.trim();
331+
332+
expect(optionElementDescription).toMatch(/option description/i);
313333
});
314334
});
315335

src/material/chips/chip-row.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
[tabIndex]="tabIndex"
1515
[disabled]="disabled"
1616
[attr.aria-label]="ariaLabel"
17-
[attr.aria-description]="ariaDescription">
17+
[attr.aria-describedby]="_ariaDescriptionId">
1818
<span class="mdc-evolution-chip__graphic mat-mdc-chip-graphic" *ngIf="leadingIcon">
1919
<ng-content select="mat-chip-avatar, [matChipAvatar]"></ng-content>
2020
</span>
@@ -38,3 +38,5 @@
3838
*ngIf="_hasTrailingIcon()">
3939
<ng-content select="mat-chip-trailing-icon,[matChipRemove],[matChipTrailingIcon]"></ng-content>
4040
</span>
41+
42+
<span class="cdk-visually-hidden" [id]="_ariaDescriptionId">{{ariaDescription}}</span>

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,15 +340,35 @@ describe('MDC-based Row Chips', () => {
340340

341341
fixture.detectChanges();
342342

343-
const primaryGridCell = fixture.nativeElement.querySelector(
343+
const primaryGridCell = (fixture.nativeElement as HTMLElement).querySelector(
344344
'[role="gridcell"].mdc-evolution-chip__cell--primary .mat-mdc-chip-action',
345345
);
346346
expect(primaryGridCell)
347347
.withContext('expected to find the grid cell for the primary chip action')
348348
.toBeTruthy();
349349

350-
expect(primaryGridCell.getAttribute('aria-label')).toBe('chip name');
351-
expect(primaryGridCell.getAttribute('aria-description')).toBe('chip description');
350+
expect(primaryGridCell!.getAttribute('aria-label')).toMatch(/chip name/i);
351+
352+
const primaryGridCellDescribedBy = primaryGridCell!.getAttribute('aria-describedby');
353+
expect(primaryGridCellDescribedBy)
354+
.withContext('expected primary grid cell to have a non-empty aria-describedby attribute')
355+
.toBeTruthy();
356+
357+
const primaryGridCellDescriptions = Array.from(
358+
(fixture.nativeElement as HTMLElement).querySelectorAll(
359+
primaryGridCellDescribedBy!
360+
.split(/\s+/g)
361+
.map(x => `#${x}`)
362+
.join(','),
363+
),
364+
);
365+
366+
const primaryGridCellDescription = primaryGridCellDescriptions
367+
.map(x => x.textContent?.trim())
368+
.join(' ')
369+
.trim();
370+
371+
expect(primaryGridCellDescription).toMatch(/chip description/i);
352372
});
353373
});
354374
});

src/material/chips/chip.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,21 @@ export class MatChip
147147
/** A unique id for the chip. If none is supplied, it will be auto-generated. */
148148
@Input() id: string = `mat-mdc-chip-${uid++}`;
149149

150+
// TODO(#26104): Consider deprecating and using `_computeAriaAccessibleName` instead.
151+
// `ariaLabel` may be unnecessary, and `_computeAriaAccessibleName` only supports
152+
// datepicker's use case.
150153
/** ARIA label for the content of the chip. */
151154
@Input('aria-label') ariaLabel: string | null = null;
152155

156+
// TODO(#26104): Consider deprecating and using `_computeAriaAccessibleName` instead.
157+
// `ariaDescription` may be unnecessary, and `_computeAriaAccessibleName` only supports
158+
// datepicker's use case.
153159
/** ARIA description for the content of the chip. */
154160
@Input('aria-description') ariaDescription: string | null = null;
155161

162+
/** Id of a span that contains this chip's aria description. */
163+
_ariaDescriptionId = `${this.id}-aria-description`;
164+
156165
private _textElement!: HTMLElement;
157166

158167
/**

tools/public_api_guard/material/chips.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export class MatChip extends _MatChipMixinBase implements AfterViewInit, CanColo
6565
constructor(_changeDetectorRef: ChangeDetectorRef, elementRef: ElementRef<HTMLElement>, _ngZone: NgZone, _focusMonitor: FocusMonitor, _document: any, animationMode?: string, _globalRippleOptions?: RippleGlobalOptions | undefined, tabIndex?: string);
6666
_animationsDisabled: boolean;
6767
ariaDescription: string | null;
68+
_ariaDescriptionId: string;
6869
ariaLabel: string | null;
6970
protected basicChipAttrName: string;
7071
// (undocumented)

0 commit comments

Comments
 (0)