Skip to content

Commit f417783

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 744a59d commit f417783

File tree

3 files changed

+21
-7
lines changed

3 files changed

+21
-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: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {createMissingDateImplError} from './datepicker-errors';
3939
import {DateFilterFn, dateInputsHaveChanged} from './datepicker-input-base';
4040
import {MatDateRangePickerInput} from './date-range-picker';
4141
import {DateRange, MatDateSelectionModel} from './date-selection-model';
42+
import {MatDatepickerIntl} from './datepicker-intl';
4243

4344
let nextUniqueId = 0;
4445

@@ -253,6 +254,7 @@ export class MatDateRangeInput<D>
253254
constructor(
254255
private _changeDetectorRef: ChangeDetectorRef,
255256
private _elementRef: ElementRef<HTMLElement>,
257+
private _intl: MatDatepickerIntl,
256258
@Optional() @Self() control: ControlContainer,
257259
@Optional() private _dateAdapter: DateAdapter<D>,
258260
@Optional() @Inject(MAT_FORM_FIELD) private _formField?: MatFormField,
@@ -380,7 +382,7 @@ export class MatDateRangeInput<D>
380382
);
381383
}
382384

383-
/** Gets the value for the `aria-labelledby` attribute of the inputs. */
385+
/** Gets the value for the `aria-labelledby` attribute of the group. */
384386
_getAriaLabelledby() {
385387
const formField = this._formField;
386388
return formField && formField._hasFloatingLabel() ? formField._labelId : null;
@@ -392,6 +394,16 @@ export class MatDateRangeInput<D>
392394
this.stateChanges.next();
393395
}
394396

397+
/** Gets the value for the aria label for the start date input. */
398+
_getStartDateLabel() {
399+
return this._intl.startDateLabel;
400+
}
401+
402+
/** Gets the value for the aria label for the end date input. */
403+
_getEndDateLabel() {
404+
return this._intl.endDateLabel;
405+
}
406+
395407
/** Re-runs the validators on the start/end inputs. */
396408
private _revalidate() {
397409
if (this._startInput) {

0 commit comments

Comments
 (0)