Skip to content

Commit a405ce6

Browse files
feat(material/form-field): make mat-errors more polite (#21870)
Make the "politeness" of mat-error "polite" by default (instead of "assertive") and also make this configurable via an input binding. Fixes #21781
1 parent b537ba1 commit a405ce6

File tree

7 files changed

+30
-13
lines changed

7 files changed

+30
-13
lines changed

src/material-experimental/mdc-chips/chip-grid.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -905,11 +905,11 @@ describe('MDC-based MatChipGrid', () => {
905905
});
906906
}));
907907

908-
it('should set the proper role on the error messages', () => {
908+
it('should set the proper aria-live attribute on the error messages', () => {
909909
errorTestComponent.formControl.markAsTouched();
910910
fixture.detectChanges();
911911

912-
expect(containerEl.querySelector('mat-error')!.getAttribute('role')).toBe('alert');
912+
expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
913913
});
914914

915915
it('sets the aria-describedby to reference errors when in error state', () => {

src/material-experimental/mdc-form-field/directives/error.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Directive, InjectionToken, Input} from '@angular/core';
9+
import {Attribute, Directive, ElementRef, InjectionToken, Input} from '@angular/core';
1010

1111
let nextUniqueId = 0;
1212

@@ -22,11 +22,19 @@ export const MAT_ERROR = new InjectionToken<MatError>('MatError');
2222
selector: 'mat-error',
2323
host: {
2424
'class': 'mat-mdc-form-field-error mat-mdc-form-field-bottom-align',
25-
'role': 'alert',
25+
'aria-atomic': 'true',
2626
'[id]': 'id',
2727
},
2828
providers: [{provide: MAT_ERROR, useExisting: MatError}],
2929
})
3030
export class MatError {
3131
@Input() id: string = `mat-mdc-error-${nextUniqueId++}`;
32+
33+
constructor(@Attribute('aria-live') ariaLive: string, elementRef: ElementRef) {
34+
// If no aria-live value is set add 'polite' as a default. This is preferred over setting
35+
// role='alert' so that screen readers do not interrupt the current task to read this aloud.
36+
if (!ariaLive) {
37+
elementRef.nativeElement.setAttribute('aria-live', 'polite');
38+
}
39+
}
3240
}

src/material-experimental/mdc-input/input.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1044,11 +1044,11 @@ describe('MatMdcInput with forms', () => {
10441044
.toBe(1, 'Expected one hint to still be shown.');
10451045
}));
10461046

1047-
it('should set the proper role on the error messages', fakeAsync(() => {
1047+
it('should set the proper aria-live attribute on the error messages', fakeAsync(() => {
10481048
testComponent.formControl.markAsTouched();
10491049
fixture.detectChanges();
10501050

1051-
expect(containerEl.querySelector('mat-error')!.getAttribute('role')).toBe('alert');
1051+
expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
10521052
}));
10531053

10541054
it('sets the aria-describedby to reference errors when in error state', fakeAsync(() => {

src/material/chips/chip-list.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1282,11 +1282,11 @@ describe('MatChipList', () => {
12821282
});
12831283
}));
12841284

1285-
it('should set the proper role on the error messages', () => {
1285+
it('should set the proper aria-live attribute on the error messages', () => {
12861286
errorTestComponent.formControl.markAsTouched();
12871287
fixture.detectChanges();
12881288

1289-
expect(containerEl.querySelector('mat-error')!.getAttribute('role')).toBe('alert');
1289+
expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
12901290
});
12911291

12921292
it('sets the aria-describedby to reference errors when in error state', () => {

src/material/form-field/error.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Directive, InjectionToken, Input} from '@angular/core';
9+
import {Attribute, Directive, ElementRef, InjectionToken, Input} from '@angular/core';
1010

1111
let nextUniqueId = 0;
1212

@@ -22,11 +22,19 @@ export const MAT_ERROR = new InjectionToken<MatError>('MatError');
2222
selector: 'mat-error',
2323
host: {
2424
'class': 'mat-error',
25-
'role': 'alert',
2625
'[attr.id]': 'id',
26+
'aria-atomic': 'true',
2727
},
2828
providers: [{provide: MAT_ERROR, useExisting: MatError}],
2929
})
3030
export class MatError {
3131
@Input() id: string = `mat-error-${nextUniqueId++}`;
32+
33+
constructor(@Attribute('aria-live') ariaLive: string, elementRef: ElementRef) {
34+
// If no aria-live value is set add 'polite' as a default. This is preferred over setting
35+
// role='alert' so that screen readers do not interrupt the current task to read this aloud.
36+
if (!ariaLive) {
37+
elementRef.nativeElement.setAttribute('aria-live', 'polite');
38+
}
39+
}
3240
}

src/material/input/input.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1193,11 +1193,11 @@ describe('MatInput with forms', () => {
11931193
.toBe(1, 'Expected one hint to still be shown.');
11941194
}));
11951195

1196-
it('should set the proper role on the error messages', fakeAsync(() => {
1196+
it('should set the proper aria-live attribute on the error messages', fakeAsync(() => {
11971197
testComponent.formControl.markAsTouched();
11981198
fixture.detectChanges();
11991199

1200-
expect(containerEl.querySelector('mat-error')!.getAttribute('role')).toBe('alert');
1200+
expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
12011201
}));
12021202

12031203
it('sets the aria-describedby to reference errors when in error state', fakeAsync(() => {

tools/public_api_guard/material/form-field.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ export declare const MAT_SUFFIX: InjectionToken<MatSuffix>;
2020

2121
export declare class MatError {
2222
id: string;
23+
constructor(ariaLive: string, elementRef: ElementRef);
2324
static ɵdir: i0.ɵɵDirectiveDefWithMeta<MatError, "mat-error", never, { "id": "id"; }, {}, never>;
24-
static ɵfac: i0.ɵɵFactoryDef<MatError, never>;
25+
static ɵfac: i0.ɵɵFactoryDef<MatError, [{ attribute: "aria-live"; }, null]>;
2526
}
2627

2728
export declare class MatFormField extends _MatFormFieldMixinBase implements AfterContentInit, AfterContentChecked, AfterViewInit, OnDestroy, CanColor {

0 commit comments

Comments
 (0)