Skip to content

Commit bdf18a2

Browse files
feat(option): add Input to remove aria-selected='false'
Add an Input to optionally remove the aria-selected attribute from unselected options. The default behavior is unchanged. Motivation: the screen reader NVDA announces 'not selected' on any element that has aria-selected='false', which is disruptive when a user is navigating through a long list of options. The W3 aria best practices example only uses aria-selected='true', false is implicit: https://www.w3.org/TR/wai-aria-practices/examples/listbox/listbox-collapsible.html
1 parent f889547 commit bdf18a2

File tree

2 files changed

+42
-7
lines changed

2 files changed

+42
-7
lines changed

src/lib/core/option/option.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export const MAT_OPTION_PARENT_COMPONENT =
7272
'[class.mat-option-multiple]': 'multiple',
7373
'[class.mat-active]': 'active',
7474
'[id]': 'id',
75-
'[attr.aria-selected]': 'selected.toString()',
75+
'[attr.aria-selected]': '_getAriaSelected()',
7676
'[attr.aria-disabled]': 'disabled.toString()',
7777
'[class.mat-option-disabled]': 'disabled',
7878
'(click)': '_selectViaInteraction()',
@@ -220,6 +220,11 @@ export class MatOption implements AfterViewChecked, OnDestroy {
220220
}
221221
}
222222

223+
/** Returns the aria-selected value for the option. */
224+
_getAriaSelected(): string|null {
225+
return this.selected || this.multiple ? this.selected.toString() : null;
226+
}
227+
223228
/** Returns the correct tabindex for the option depending on disabled state. */
224229
_getTabIndex(): string {
225230
return this.disabled ? '-1' : '0';

src/lib/select/select.spec.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -882,10 +882,10 @@ describe('MatSelect', () => {
882882
expect(options[2].getAttribute('role')).toEqual('option');
883883
}));
884884

885-
it('should set aria-selected on each option', fakeAsync(() => {
886-
expect(options[0].getAttribute('aria-selected')).toEqual('false');
887-
expect(options[1].getAttribute('aria-selected')).toEqual('false');
888-
expect(options[2].getAttribute('aria-selected')).toEqual('false');
885+
it('should set aria-selected on each option for single select', fakeAsync(() => {
886+
expect(options[0].getAttribute('aria-selected')).toEqual(null);
887+
expect(options[1].getAttribute('aria-selected')).toEqual(null);
888+
expect(options[2].getAttribute('aria-selected')).toEqual(null);
889889

890890
options[1].click();
891891
fixture.detectChanges();
@@ -894,11 +894,41 @@ describe('MatSelect', () => {
894894
fixture.detectChanges();
895895
flush();
896896

897-
expect(options[0].getAttribute('aria-selected')).toEqual('false');
897+
expect(options[0].getAttribute('aria-selected')).toEqual(null);
898898
expect(options[1].getAttribute('aria-selected')).toEqual('true');
899-
expect(options[2].getAttribute('aria-selected')).toEqual('false');
899+
expect(options[2].getAttribute('aria-selected')).toEqual(null);
900900
}));
901901

902+
it('should set aria-selected on each option for multi-select', fakeAsync(() => {
903+
fixture.destroy();
904+
905+
const multiFixture = TestBed.createComponent(MultiSelect);
906+
multiFixture.detectChanges();
907+
908+
trigger = multiFixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
909+
trigger.click();
910+
multiFixture.detectChanges();
911+
912+
options =
913+
overlayContainerElement.querySelectorAll('mat-option') as NodeListOf<HTMLElement>;
914+
915+
expect(options[0].getAttribute('aria-selected')).toEqual('false');
916+
expect(options[1].getAttribute('aria-selected')).toEqual('false');
917+
expect(options[2].getAttribute('aria-selected')).toEqual('false');
918+
919+
options[1].click();
920+
multiFixture.detectChanges();
921+
922+
trigger.click();
923+
multiFixture.detectChanges();
924+
flush();
925+
926+
expect(options[0].getAttribute('aria-selected')).toEqual('false');
927+
expect(options[1].getAttribute('aria-selected')).toEqual('true');
928+
expect(options[2].getAttribute('aria-selected')).toEqual('false');
929+
}));
930+
931+
902932
it('should set the tabindex of each option according to disabled state', fakeAsync(() => {
903933
expect(options[0].getAttribute('tabindex')).toEqual('0');
904934
expect(options[1].getAttribute('tabindex')).toEqual('0');

0 commit comments

Comments
 (0)