diff --git a/packages/mui-material/src/Select/Select.test.js b/packages/mui-material/src/Select/Select.test.js
index 273f9f51caf3ad..bb00594406304f 100644
--- a/packages/mui-material/src/Select/Select.test.js
+++ b/packages/mui-material/src/Select/Select.test.js
@@ -11,6 +11,7 @@ import {
} from 'test/utils';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import MenuItem from '@mui/material/MenuItem';
+import ListSubheader from '@mui/material/ListSubheader';
import InputBase from '@mui/material/InputBase';
import OutlinedInput from '@mui/material/OutlinedInput';
import InputLabel from '@mui/material/InputLabel';
@@ -484,6 +485,88 @@ describe('', () => {
expect(getAllByRole('option')[1]).to.have.attribute('aria-selected', 'true');
});
+ describe('when the first child is a ListSubheader', () => {
+ it('first selectable option is focused to use the arrow', () => {
+ const { getAllByRole } = render(
+ ,
+ );
+
+ const options = getAllByRole('option');
+ expect(options[1]).to.have.attribute('tabindex', '0');
+
+ act(() => {
+ fireEvent.keyDown(options[1], { key: 'ArrowDown' });
+ fireEvent.keyDown(options[2], { key: 'ArrowDown' });
+ fireEvent.keyDown(options[4], { key: 'Enter' });
+ });
+
+ expect(options[4]).to.have.attribute('aria-selected', 'true');
+ });
+
+ describe('when also the second child is a ListSubheader', () => {
+ it('first selectable option is focused to use the arrow', () => {
+ const { getAllByRole } = render(
+ ,
+ );
+
+ const options = getAllByRole('option');
+ expect(options[2]).to.have.attribute('tabindex', '0');
+
+ act(() => {
+ fireEvent.keyDown(options[2], { key: 'ArrowDown' });
+ fireEvent.keyDown(options[3], { key: 'ArrowDown' });
+ fireEvent.keyDown(options[5], { key: 'Enter' });
+ });
+
+ expect(options[5]).to.have.attribute('aria-selected', 'true');
+ });
+ });
+ });
+
+ describe('when the first child is a MenuItem disabled', () => {
+ it('first selectable option is focused to use the arrow', () => {
+ const { getAllByRole } = render(
+ ,
+ );
+
+ const options = getAllByRole('option');
+ expect(options[2]).to.have.attribute('tabindex', '0');
+
+ act(() => {
+ fireEvent.keyDown(options[2], { key: 'ArrowDown' });
+ fireEvent.keyDown(options[3], { key: 'ArrowDown' });
+ fireEvent.keyDown(options[5], { key: 'Enter' });
+ });
+
+ expect(options[5]).to.have.attribute('aria-selected', 'true');
+ });
+ });
+
it('it will fallback to its content for the accessible name when it has no name', () => {
const { getByRole } = render();
diff --git a/packages/mui-material/src/Select/SelectInput.js b/packages/mui-material/src/Select/SelectInput.js
index 1c20c299bb496c..2fb310e5eb26b7 100644
--- a/packages/mui-material/src/Select/SelectInput.js
+++ b/packages/mui-material/src/Select/SelectInput.js
@@ -348,7 +348,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
}
}
- const items = childrenArray.map((child) => {
+ const items = childrenArray.map((child, index, arr) => {
if (!React.isValidElement(child)) {
return null;
}
@@ -389,6 +389,26 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
foundMatch = true;
}
+ if (child.props.value === undefined) {
+ return React.cloneElement(child, {
+ 'aria-readonly': true,
+ role: 'option',
+ });
+ }
+
+ const isFirstSelectableElement = () => {
+ if (value) {
+ return selected;
+ }
+ const firstSelectableElement = arr.find(
+ (item) => item.props.value !== undefined && item.props.disabled !== true,
+ );
+ if (child === firstSelectableElement) {
+ return true;
+ }
+ return selected;
+ };
+
return React.cloneElement(child, {
'aria-selected': selected ? 'true' : 'false',
onClick: handleItemClick(child),
@@ -405,7 +425,10 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
}
},
role: 'option',
- selected,
+ selected:
+ arr[0].props.value === undefined || arr[0].props.disabled === true
+ ? isFirstSelectableElement()
+ : selected,
value: undefined, // The value is most likely not a valid HTML attribute.
'data-value': child.props.value, // Instead, we provide it as a data attribute.
});