Skip to content

Commit 741ecb1

Browse files
committed
fix(material/chips): implement ariaDescription with aria-describedby
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 547357a commit 741ecb1

File tree

7 files changed

+67
-7
lines changed

7 files changed

+67
-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-option.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export class MatChipSelectionChange {
3131
) {}
3232
}
3333

34+
let id = 1;
35+
3436
/**
3537
* An extension of the MatChip component that supports chip selection. Used with MatChipListbox.
3638
*
@@ -178,4 +180,7 @@ export class MatChipOption extends MatChip implements OnInit {
178180
this._changeDetectorRef.markForCheck();
179181
}
180182
}
183+
184+
/** Id of a span that contains this chip's aria description. @docs-private */
185+
_ariaDescriptionId = `chip-option-${id++}-aria-description`;
181186
}

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-row.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import {MatChipEditInput} from './chip-edit-input';
3333
import {takeUntil} from 'rxjs/operators';
3434
import {MAT_CHIP} from './tokens';
3535

36+
let id = 1;
37+
3638
/** Represents an event fired on an individual `mat-chip` when it is edited. */
3739
export interface MatChipEditedEvent extends MatChipEvent {
3840
/** The final edit value. */
@@ -212,4 +214,7 @@ export class MatChipRow extends MatChip implements AfterViewInit {
212214
private _getEditInput(): MatChipEditInput {
213215
return this.contentEditInput || this.defaultEditInput!;
214216
}
217+
218+
/** Id of a span that contains this chip's aria description. @docs-private */
219+
_ariaDescriptionId = `chip-row-${id++}-aria-description`;
215220
}

src/material/chips/chip.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,15 @@ 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

0 commit comments

Comments
 (0)