Skip to content

Commit 97410fa

Browse files
authored
fix(material/core): remove tabindex from mat-option (#26917)
Remove the tabindex attribute added to MatOption components. MatOption does not need tabindex because the parent component manages focus by setting `aria-activedescendant` attribute. Previously, MatOption set tabindex but was also a referenced by aria-activedescendant. This was not the correct ARIA semantics. Align closer to ARIA spec by using only aria-activedescendant rather than both. Tabindex="-1" seems to be causing a problem in #26861 where VoiceOver with Firefox moves DOM focus from the combobox to the option when opening the listbox popup. Unblocks #26861.
1 parent afa6400 commit 97410fa

File tree

2 files changed

+22
-10
lines changed

2 files changed

+22
-10
lines changed

src/material/core/option/option.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,9 @@ export class _MatOptionBase<T = any> implements FocusableOption, AfterViewChecke
205205
}
206206

207207
/** Returns the correct tabindex for the option depending on disabled state. */
208+
// This method is only used by `MatLegacyOption`. Keeping it here to avoid breaking the types.
209+
// That's because `MatLegacyOption` use `MatOption` type in a few places such as
210+
// `MatOptionSelectionChange`. It is safe to delete this when `MatLegacyOption` is deleted.
208211
_getTabIndex(): string {
209212
return this.disabled ? '-1' : '0';
210213
}
@@ -251,7 +254,6 @@ export class _MatOptionBase<T = any> implements FocusableOption, AfterViewChecke
251254
exportAs: 'matOption',
252255
host: {
253256
'role': 'option',
254-
'[attr.tabindex]': '_getTabIndex()',
255257
'[class.mdc-list-item--selected]': 'selected',
256258
'[class.mat-mdc-option-multiple]': 'multiple',
257259
'[class.mat-mdc-option-active]': 'active',

src/material/select/select.spec.ts

+19-9
Original file line numberDiff line numberDiff line change
@@ -824,17 +824,27 @@ describe('MDC-based MatSelect', () => {
824824
'mat-option',
825825
) as NodeListOf<HTMLElement>;
826826

827-
options[3].focus();
827+
select.focus();
828+
multiFixture.detectChanges();
829+
multiFixture.componentInstance.select._keyManager.setActiveItem(3);
830+
multiFixture.detectChanges();
831+
828832
expect(document.activeElement)
829-
.withContext('Expected fourth option to be focused.')
830-
.toBe(options[3]);
833+
.withContext('Expected select to have DOM focus.')
834+
.toBe(select);
835+
expect(select.getAttribute('aria-activedescendant'))
836+
.withContext('Expected fourth option to be activated.')
837+
.toBe(options[3].id);
831838

832839
multiFixture.componentInstance.control.setValue(['steak-0', 'sushi-7']);
833840
multiFixture.detectChanges();
834841

835842
expect(document.activeElement)
836-
.withContext('Expected fourth option to remain focused.')
837-
.toBe(options[3]);
843+
.withContext('Expected select to have DOM focus.')
844+
.toBe(select);
845+
expect(select.getAttribute('aria-activedescendant'))
846+
.withContext('Expected fourth optino to remain activated.')
847+
.toBe(options[3].id);
838848
}),
839849
);
840850

@@ -1260,10 +1270,10 @@ describe('MDC-based MatSelect', () => {
12601270
.toBe(true);
12611271
}));
12621272

1263-
it('should set the tabindex of each option according to disabled state', fakeAsync(() => {
1264-
expect(options[0].getAttribute('tabindex')).toEqual('0');
1265-
expect(options[1].getAttribute('tabindex')).toEqual('0');
1266-
expect(options[2].getAttribute('tabindex')).toEqual('-1');
1273+
it('should omit the tabindex attribute on each option', fakeAsync(() => {
1274+
expect(options[0].hasAttribute('tabindex')).toBeFalse();
1275+
expect(options[1].hasAttribute('tabindex')).toBeFalse();
1276+
expect(options[2].hasAttribute('tabindex')).toBeFalse();
12671277
}));
12681278

12691279
it('should set aria-disabled for disabled options', fakeAsync(() => {

0 commit comments

Comments
 (0)