Skip to content

Commit 4c8f505

Browse files
authored
fix(material/core): mat-option sets aria-selected="false" (#26673)
For mat-option, set `aria-selected="false"` on deselected options. Confirms with [WAI ARIA Listbox authoring practices guide](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/), which says to always include aria-selected attribute on listbox options that can be selected. Fix issue where VoiceOver reads every option as "selected" (21491). Fix #21491
1 parent 7043a51 commit 4c8f505

File tree

5 files changed

+19
-21
lines changed

5 files changed

+19
-21
lines changed

src/material/core/option/option.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -204,16 +204,6 @@ export class _MatOptionBase<T = any> implements FocusableOption, AfterViewChecke
204204
}
205205
}
206206

207-
/**
208-
* Gets the `aria-selected` value for the option. We explicitly omit the `aria-selected`
209-
* attribute from single-selection, unselected options. Including the `aria-selected="false"`
210-
* attributes adds a significant amount of noise to screen-reader users without providing useful
211-
* information.
212-
*/
213-
_getAriaSelected(): boolean | null {
214-
return this.selected || (this.multiple ? false : null);
215-
}
216-
217207
/** Returns the correct tabindex for the option depending on disabled state. */
218208
_getTabIndex(): string {
219209
return this.disabled ? '-1' : '0';
@@ -267,7 +257,16 @@ export class _MatOptionBase<T = any> implements FocusableOption, AfterViewChecke
267257
'[class.mat-mdc-option-active]': 'active',
268258
'[class.mdc-list-item--disabled]': 'disabled',
269259
'[id]': 'id',
270-
'[attr.aria-selected]': '_getAriaSelected()',
260+
// Set aria-selected to false for non-selected items and true for selected items. Conform to
261+
// [WAI ARIA Listbox authoring practices guide](
262+
// https://www.w3.org/WAI/ARIA/apg/patterns/listbox/), "If any options are selected, each
263+
// selected option has either aria-selected or aria-checked set to true. All options that are
264+
// selectable but not selected have either aria-selected or aria-checked set to false." Align
265+
// aria-selected implementation of Chips and List components.
266+
//
267+
// Set `aria-selected="false"` on not-selected listbox options to fix VoiceOver announcing
268+
// every option as "selected" (#21491).
269+
'[attr.aria-selected]': 'selected',
271270
'[attr.aria-disabled]': 'disabled.toString()',
272271
'(click)': '_selectViaInteraction()',
273272
'(keydown)': '_handleKeydown($event)',

src/material/legacy-core/option/option.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import {MatLegacyOptgroup} from './optgroup';
3838
'[class.mat-option-multiple]': 'multiple',
3939
'[class.mat-active]': 'active',
4040
'[id]': 'id',
41-
'[attr.aria-selected]': '_getAriaSelected()',
41+
'[attr.aria-selected]': 'selected',
4242
'[attr.aria-disabled]': 'disabled.toString()',
4343
'[class.mat-option-disabled]': 'disabled',
4444
'(click)': '_selectViaInteraction()',

src/material/legacy-select/select.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,9 +1151,9 @@ describe('MatSelect', () => {
11511151
}));
11521152

11531153
it('should set aria-selected on each option for single select', fakeAsync(() => {
1154-
expect(options.every(option => !option.hasAttribute('aria-selected')))
1154+
expect(options.every(option => option.getAttribute('aria-selected') === 'false'))
11551155
.withContext(
1156-
'Expected all unselected single-select options not to have ' + 'aria-selected set.',
1156+
'Expected all unselected single-select options to have aria-selected="false".',
11571157
)
11581158
.toBe(true);
11591159

@@ -1168,9 +1168,9 @@ describe('MatSelect', () => {
11681168
.withContext('Expected selected single-select option to have aria-selected="true".')
11691169
.toEqual('true');
11701170
options.splice(1, 1);
1171-
expect(options.every(option => !option.hasAttribute('aria-selected')))
1171+
expect(options.every(option => option.getAttribute('aria-selected') === 'false'))
11721172
.withContext(
1173-
'Expected all unselected single-select options not to have ' + 'aria-selected set.',
1173+
'Expected all unselected single-select options to have aria-selected="false".',
11741174
)
11751175
.toBe(true);
11761176
}));

src/material/select/select.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,9 +1184,9 @@ describe('MDC-based MatSelect', () => {
11841184
}));
11851185

11861186
it('should set aria-selected on each option for single select', fakeAsync(() => {
1187-
expect(options.every(option => !option.hasAttribute('aria-selected')))
1187+
expect(options.every(option => option.getAttribute('aria-selected') === 'false'))
11881188
.withContext(
1189-
'Expected all unselected single-select options not to have ' + 'aria-selected set.',
1189+
'Expected all unselected single-select options to have ' + 'aria-selected="false".',
11901190
)
11911191
.toBe(true);
11921192

@@ -1203,9 +1203,9 @@ describe('MDC-based MatSelect', () => {
12031203
)
12041204
.toEqual('true');
12051205
options.splice(1, 1);
1206-
expect(options.every(option => !option.hasAttribute('aria-selected')))
1206+
expect(options.every(option => option.getAttribute('aria-selected') === 'false'))
12071207
.withContext(
1208-
'Expected all unselected single-select options not to have ' + 'aria-selected set.',
1208+
'Expected all unselected single-select options to have ' + 'aria-selected="false".',
12091209
)
12101210
.toBe(true);
12111211
}));

tools/public_api_guard/material/core.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,6 @@ export class _MatOptionBase<T = any> implements FocusableOption, AfterViewChecke
279279
set disabled(value: BooleanInput);
280280
get disableRipple(): boolean;
281281
focus(_origin?: FocusOrigin, options?: FocusOptions): void;
282-
_getAriaSelected(): boolean | null;
283282
_getHostElement(): HTMLElement;
284283
getLabel(): string;
285284
_getTabIndex(): string;

0 commit comments

Comments
 (0)