Skip to content

Commit 13434c5

Browse files
committed
fix(material/datepicker): add aria labels to <input/>s for Start/End Date
Give `matDateStart` aria label of "Start Date" and give `matDateEnd` a label of "End Date" by wrapping in a `<label>` element. Apply the aria label of the form field to the `<mat-date-range-input>` component, which has role of group. Previously the placeholder was used to communicate which of the inputs was the start date and which was the end date. Only affects the DOM structure and a11y tree. Does not change the visual appearance. Consider the [Basic date range picker example](https://material.angular.io/components/datepicker/overview#date-range-picker-overview): ``` <mat-form-field appearance="fill"> <mat-label>Enter a date range</mat-label> <mat-date-range-input [rangePicker]="picker"> <input matStartDate placeholder="Start date"> <input matEndDate placeholder="End date"> </mat-date-range-input> ... </mat-form-field> ``` Previously, it would produce an accessibility tree that looks something like this. ``` group "Enter a date range" LabelText StaticText "Enter a date range" textbox "Enter a date range" Textbox "End date" ``` Problems with this approach. 1. Screen reader does not announce "Start Date" right away or not at 2. "Start date"/"End date" come from the placeholder put a label would be more appropriate. With this commit applied, accessibility is consistent between both inputs, and it is easier to tell which of the two is the start and which is the end. ``` group "Enter a date range" LabelText textbox "Start Date" LabelText textbox "End Date" ``` Fixes: #23445
1 parent f74c449 commit 13434c5

File tree

4 files changed

+26
-7
lines changed

4 files changed

+26
-7
lines changed

src/material/datepicker/date-range-input.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@
22
class="mat-date-range-input-container"
33
cdkMonitorSubtreeFocus
44
(cdkFocusChange)="_updateFocus($event)">
5-
<div class="mat-date-range-input-start-wrapper">
5+
<label class="mat-date-range-input-start-wrapper">
66
<ng-content select="input[matStartDate]"></ng-content>
77
<span
88
class="mat-date-range-input-mirror"
99
aria-hidden="true">{{_getInputMirrorValue()}}</span>
10-
</div>
10+
<span class="cdk-visually-hidden">{{_getStartDateLabel()}}</span>
11+
</label>
1112

1213
<span
1314
class="mat-date-range-input-separator"
1415
[class.mat-date-range-input-separator-hidden]="_shouldHideSeparator()">{{separator}}</span>
1516

16-
<div class="mat-date-range-input-end-wrapper">
17+
<label class="mat-date-range-input-end-wrapper">
1718
<ng-content select="input[matEndDate]"></ng-content>
18-
</div>
19+
<span class="cdk-visually-hidden">{{_getEndDateLabel()}}</span>
20+
</label>
1921
</div>
2022

src/material/datepicker/date-range-input.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ describe('MatDateRangeInput', () => {
160160
it('should point the label aria-owns to the id of the start input', () => {
161161
const fixture = createComponent(StandardRangePicker);
162162
fixture.detectChanges();
163-
const label = fixture.nativeElement.querySelector('label');
163+
const label = fixture.nativeElement.querySelector('label.mat-form-field-label');
164164
const start = fixture.componentInstance.start.nativeElement;
165165

166166
expect(start.id).toBeTruthy();
@@ -170,7 +170,7 @@ describe('MatDateRangeInput', () => {
170170
it('should point the range input aria-labelledby to the form field label', () => {
171171
const fixture = createComponent(StandardRangePicker);
172172
fixture.detectChanges();
173-
const labelId = fixture.nativeElement.querySelector('label').id;
173+
const labelId = fixture.nativeElement.querySelector('label.mat-form-field-label').id;
174174
const rangeInput = fixture.nativeElement.querySelector('.mat-date-range-input');
175175

176176
expect(labelId).toBeTruthy();

src/material/datepicker/date-range-input.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
Inject,
2222
OnChanges,
2323
SimpleChanges,
24+
inject,
25+
InjectFlags,
2426
} from '@angular/core';
2527
import {MatFormFieldControl, MatFormField, MAT_FORM_FIELD} from '@angular/material/form-field';
2628
import {ThemePalette, DateAdapter} from '@angular/material/core';
@@ -39,6 +41,7 @@ import {createMissingDateImplError} from './datepicker-errors';
3941
import {DateFilterFn, dateInputsHaveChanged} from './datepicker-input-base';
4042
import {MatDateRangePickerInput} from './date-range-picker';
4143
import {DateRange, MatDateSelectionModel} from './date-selection-model';
44+
import {MatDatepickerIntl} from './datepicker-intl';
4245

4346
let nextUniqueId = 0;
4447

@@ -137,6 +140,8 @@ export class MatDateRangeInput<D>
137140
}
138141
private _required: boolean;
139142

143+
private readonly _intl = inject(MatDatepickerIntl, InjectFlags.Optional);
144+
140145
/** Function that can be used to filter out dates within the date range picker. */
141146
@Input()
142147
get dateFilter() {
@@ -380,7 +385,7 @@ export class MatDateRangeInput<D>
380385
);
381386
}
382387

383-
/** Gets the value for the `aria-labelledby` attribute of the inputs. */
388+
/** Gets the value for the `aria-labelledby` attribute of the group. */
384389
_getAriaLabelledby() {
385390
const formField = this._formField;
386391
return formField && formField._hasFloatingLabel() ? formField._labelId : null;
@@ -392,6 +397,16 @@ export class MatDateRangeInput<D>
392397
this.stateChanges.next();
393398
}
394399

400+
/** Gets the value for the aria label for the start date input. */
401+
_getStartDateLabel(): string | null {
402+
return this._intl ? this._intl.startDateLabel : null;
403+
}
404+
405+
/** Gets the value for the aria label for the end date input. */
406+
_getEndDateLabel(): string | null {
407+
return this._intl ? this._intl.endDateLabel : null;
408+
}
409+
395410
/** Re-runs the validators on the start/end inputs. */
396411
private _revalidate() {
397412
if (this._startInput) {

tools/public_api_guard/material/datepicker.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,8 +621,10 @@ export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
621621
focused: boolean;
622622
_getAriaLabelledby(): string | null;
623623
getConnectedOverlayOrigin(): ElementRef;
624+
_getEndDateLabel(): string | null;
624625
_getInputMirrorValue(): string;
625626
getOverlayLabelId(): string | null;
627+
_getStartDateLabel(): string | null;
626628
getStartValue(): D | null;
627629
getThemePalette(): ThemePalette;
628630
// (undocumented)

0 commit comments

Comments
 (0)