Skip to content

Commit 31909cc

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 799cf7c commit 31909cc

File tree

4 files changed

+25
-9
lines changed

4 files changed

+25
-9
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

@@ -255,6 +256,7 @@ export class MatDateRangeInput<D>
255256
private _elementRef: ElementRef<HTMLElement>,
256257
@Optional() @Self() control: ControlContainer,
257258
@Optional() private _dateAdapter: DateAdapter<D>,
259+
@Optional() private _intl: MatDatepickerIntl | null,
258260
@Optional() @Inject(MAT_FORM_FIELD) private _formField?: MatFormField,
259261
) {
260262
if (!_dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) {
@@ -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(): string | null {
399+
return this._intl ? this._intl.startDateLabel : null;
400+
}
401+
402+
/** Gets the value for the aria label for the end date input. */
403+
_getEndDateLabel(): string | null {
404+
return this._intl ? this._intl.endDateLabel : null;
405+
}
406+
395407
/** Re-runs the validators on the start/end inputs. */
396408
private _revalidate() {
397409
if (this._startInput) {

tools/public_api_guard/material/datepicker.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ export class MatDatepickerToggleIcon {
605605

606606
// @public (undocumented)
607607
export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>, MatDatepickerControl<D>, MatDateRangeInputParent<D>, MatDateRangePickerInput<D>, AfterContentInit, OnChanges, OnDestroy {
608-
constructor(_changeDetectorRef: ChangeDetectorRef, _elementRef: ElementRef<HTMLElement>, control: ControlContainer, _dateAdapter: DateAdapter<D>, _formField?: MatFormField | undefined);
608+
constructor(_changeDetectorRef: ChangeDetectorRef, _elementRef: ElementRef<HTMLElement>, control: ControlContainer, _dateAdapter: DateAdapter<D>, _intl: MatDatepickerIntl | null, _formField?: MatFormField | undefined);
609609
_ariaDescribedBy: string | null;
610610
comparisonEnd: D | null;
611611
comparisonStart: D | null;
@@ -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)
@@ -660,7 +662,7 @@ export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
660662
// (undocumented)
661663
static ɵcmp: i0.ɵɵComponentDeclaration<MatDateRangeInput<any>, "mat-date-range-input", ["matDateRangeInput"], { "rangePicker": "rangePicker"; "required": "required"; "dateFilter": "dateFilter"; "min": "min"; "max": "max"; "disabled": "disabled"; "separator": "separator"; "comparisonStart": "comparisonStart"; "comparisonEnd": "comparisonEnd"; }, {}, ["_startInput", "_endInput"], ["input[matStartDate]", "input[matEndDate]"], false>;
662664
// (undocumented)
663-
static ɵfac: i0.ɵɵFactoryDeclaration<MatDateRangeInput<any>, [null, null, { optional: true; self: true; }, { optional: true; }, { optional: true; }]>;
665+
static ɵfac: i0.ɵɵFactoryDeclaration<MatDateRangeInput<any>, [null, null, { optional: true; self: true; }, { optional: true; }, { optional: true; }, { optional: true; }]>;
664666
}
665667

666668
// @public

0 commit comments

Comments
 (0)