Skip to content

Commit d4f2fa6

Browse files
authored
fix(material/datepicker): screen reader not announcing view switch button value (#21229)
The button for switching between calendar views has an `aria-label` along the lines of "Choose month" which overrides its default text. This means that the user has no way of knowing what is inside the button. These changes add a further `aria-describedby` pointing to the content which lets the screen reader announce both pieces of text. Note that an alternate approach is to concatenate the content into the `aria-label`, but I don't know how well that would work in RTL languages.
1 parent e01ebf6 commit d4f2fa6

File tree

4 files changed

+17
-2
lines changed

4 files changed

+17
-2
lines changed

src/material/datepicker/calendar-header.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
<div class="mat-calendar-controls">
33
<button mat-button type="button" class="mat-calendar-period-button"
44
(click)="currentPeriodClicked()" [attr.aria-label]="periodButtonLabel"
5+
[attr.aria-describedby]="_buttonDescriptionId"
56
cdkAriaLive="polite">
6-
{{periodButtonText}}
7+
<span [attr.id]="_buttonDescriptionId">{{periodButtonText}}</span>
78
<div class="mat-calendar-arrow"
8-
[class.mat-calendar-invert]="calendar.currentView != 'month'"></div>
9+
[class.mat-calendar-invert]="calendar.currentView !== 'month'"></div>
910
</button>
1011

1112
<div class="mat-calendar-spacer"></div>

src/material/datepicker/calendar-header.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ describe('MatCalendarHeader', () => {
203203
expect(calendarInstance.currentView).toBe('multi-year');
204204
expect(periodButton.textContent).toContain('FAKE_YEAR');
205205
});
206+
207+
it('should label and describe period button for assistive technology', () => {
208+
const description = periodButton.querySelector('span[id]');
209+
expect(periodButton.hasAttribute('aria-label')).toBe(true);
210+
expect(periodButton.hasAttribute('aria-describedby')).toBe(true);
211+
expect(periodButton.getAttribute('aria-describedby')).toBe(description?.getAttribute('id')!);
212+
});
213+
206214
});
207215

208216
describe('calendar with minDate only', () => {

src/material/datepicker/calendar.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ import {MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER, DateRange} from './date-select
5050
*/
5151
export type MatCalendarView = 'month' | 'year' | 'multi-year';
5252

53+
/** Counter used to generate unique IDs. */
54+
let uniqueId = 0;
55+
5356
/** Default header for MatCalendar */
5457
@Component({
5558
selector: 'mat-calendar-header',
@@ -59,6 +62,8 @@ export type MatCalendarView = 'month' | 'year' | 'multi-year';
5962
changeDetection: ChangeDetectionStrategy.OnPush,
6063
})
6164
export class MatCalendarHeader<D> {
65+
_buttonDescriptionId = `mat-calendar-button-${uniqueId++}`;
66+
6267
constructor(private _intl: MatDatepickerIntl,
6368
@Inject(forwardRef(() => MatCalendar)) public calendar: MatCalendar<D>,
6469
@Optional() private _dateAdapter: DateAdapter<D>,

tools/public_api_guard/material/datepicker.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ export declare type MatCalendarCellCssClasses = string | string[] | Set<string>
156156
};
157157

158158
export declare class MatCalendarHeader<D> {
159+
_buttonDescriptionId: string;
159160
calendar: MatCalendar<D>;
160161
get nextButtonLabel(): string;
161162
get periodButtonLabel(): string;

0 commit comments

Comments
 (0)