Repository: mui/material-ui (TypeScript)
Date: 2019-09-25 14:01:49+00:00
Task type: Feature

Issue:
MenuList is not accessible
Creating this to track the follow-up fixes from #16644 for `MenuList`. #17506 solves the `Menu` focus issue which seems to work well, but we still need proper keyboard support in `MenuList`.

See #16644 for in-depth discussion on this issues.

| Tech        | Version |
| ----------- | ------- |
| Material-UI | v4.4.2  |



Issue discussion:
Yep thanks for opening this. I want to move the keyboard navigation to menulist and menu only handles initial focus. Basically MenuList implements listbox and Menu is just a styled wrapper.

Solution patch:
diff --git a/docs/pages/api/menu-list.md b/docs/pages/api/menu-list.md
--- a/docs/pages/api/menu-list.md
+++ b/docs/pages/api/menu-list.md
@@ -18,15 +18,20 @@ import { MenuList } from '@material-ui/core';
 
 You can learn more about the difference by [reading this guide](/guides/minimizing-bundle-size/).
 
-
+A permanently displayed menu following https://www.w3.org/TR/wai-aria-practices/#menubutton
+It's exposed to help customization of the [`Menu`](/api/menu/) component. If you
+use it separately you need to move focus into the component manually. Once
+the focus is placed inside the component it is fully keyboard accessible.
 
 ## Props
 
 | Name | Type | Default | Description |
 |:-----|:-----|:--------|:------------|
-| <span class="prop-name">autoFocus</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the list will be focused during the first mount. Focus will also be triggered if the value changes from false to true. |
+| <span class="prop-name">autoFocus</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, will focus the `[role="menu"]` container and move into tab order |
+| <span class="prop-name">autoFocusItem</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, will focus the first menuitem if `variant="menu"` or selected item if `variant="selectedMenu"` |
 | <span class="prop-name">children</span> | <span class="prop-type">node</span> |  | MenuList contents, normally `MenuItem`s. |
 | <span class="prop-name">disableListWrap</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the menu items will not wrap focus. |
+| <span class="prop-name">variant</span> | <span class="prop-type">'menu'<br>&#124;&nbsp;'selectedMenu'</span> | <span class="prop-default">'selectedMenu'</span> | The variant to use. Use `menu` to prevent selected items from impacting the initial focus and the vertical alignment relative to the anchor element. |
 
 The `ref` is forwarded to the root element.
 
diff --git a/docs/src/pages/components/menus/MenuListComposition.js b/docs/src/pages/components/menus/MenuListComposition.js
--- a/docs/src/pages/components/menus/MenuListComposition.js
+++ b/docs/src/pages/components/menus/MenuListComposition.js
@@ -34,6 +34,23 @@ export default function MenuListComposition() {
     setOpen(false);
   };
 
+  function handleListKeyDown(event) {
+    if (event.key === 'Tab') {
+      event.preventDefault();
+      setOpen(false);
+    }
+  }
+
+  // return focus to the button when we transitioned from !open -> open
+  const prevOpen = React.useRef(open);
+  React.useEffect(() => {
+    if (prevOpen.current === true && open === false) {
+      anchorRef.current.focus();
+    }
+
+    prevOpen.current = open;
+  }, [open]);
+
   return (
     <div className={classes.root}>
       <Paper className={classes.paper}>
@@ -60,7 +77,7 @@ export default function MenuListComposition() {
             >
               <Paper id="menu-list-grow">
                 <ClickAwayListener onClickAway={handleClose}>
-                  <MenuList>
+                  <MenuList autoFocusItem={open} onKeyDown={handleListKeyDown}>
                     <MenuItem onClick={handleClose}>Profile</MenuItem>
                     <MenuItem onClick={handleClose}>My account</MenuItem>
                     <MenuItem onClick={handleClose}>Logout</MenuItem>
diff --git a/docs/src/pages/components/menus/MenuListComposition.tsx b/docs/src/pages/components/menus/MenuListComposition.tsx
--- a/docs/src/pages/components/menus/MenuListComposition.tsx
+++ b/docs/src/pages/components/menus/MenuListComposition.tsx
@@ -36,6 +36,23 @@ export default function MenuListComposition() {
     setOpen(false);
   };
 
+  function handleListKeyDown(event: React.KeyboardEvent) {
+    if (event.key === 'Tab') {
+      event.preventDefault();
+      setOpen(false);
+    }
+  }
+
+  // return focus to the button when we transitioned from !open -> open
+  const prevOpen = React.useRef(open);
+  React.useEffect(() => {
+    if (prevOpen.current === true && open === false) {
+      anchorRef.current!.focus();
+    }
+
+    prevOpen.current = open;
+  }, [open]);
+
   return (
     <div className={classes.root}>
       <Paper className={classes.paper}>
@@ -62,7 +79,7 @@ export default function MenuListComposition() {
             >
               <Paper id="menu-list-grow">
                 <ClickAwayListener onClickAway={handleClose}>
-                  <MenuList>
+                  <MenuList autoFocusItem={open} onKeyDown={handleListKeyDown}>
                     <MenuItem onClick={handleClose}>Profile</MenuItem>
                     <MenuItem onClick={handleClose}>My account</MenuItem>
                     <MenuItem onClick={handleClose}>Logout</MenuItem>
diff --git a/packages/material-ui/src/Menu/Menu.js b/packages/material-ui/src/Menu/Menu.js
--- a/packages/material-ui/src/Menu/Menu.js
+++ b/packages/material-ui/src/Menu/Menu.js
@@ -116,22 +116,13 @@ const Menu = React.forwardRef(function Menu(props, ref) {
 
   const items = React.Children.map(children, (child, index) => {
     if (index === activeItemIndex) {
-      const newChildProps = {};
-      if (autoFocusItem) {
-        newChildProps.autoFocus = true;
-      }
-      if (child.props.tabIndex === undefined && variant === 'selectedMenu') {
-        newChildProps.tabIndex = 0;
-      }
-      newChildProps.ref = instance => {
-        // #StrictMode ready
-        contentAnchorRef.current = ReactDOM.findDOMNode(instance);
-        setRef(child.ref, instance);
-      };
-
-      if (newChildProps !== null) {
-        return React.cloneElement(child, newChildProps);
-      }
+      return React.cloneElement(child, {
+        ref: instance => {
+          // #StrictMode ready
+          contentAnchorRef.current = ReactDOM.findDOMNode(instance);
+          setRef(child.ref, instance);
+        },
+      });
     }
 
     return child;
@@ -162,6 +153,8 @@ const Menu = React.forwardRef(function Menu(props, ref) {
         onKeyDown={handleListKeyDown}
         actions={menuListActionsRef}
         autoFocus={autoFocus && (activeItemIndex === -1 || disableAutoFocusItem)}
+        autoFocusItem={autoFocusItem}
+        variant={variant}
         {...MenuListProps}
         className={clsx(classes.list, MenuListProps.className)}
       >
diff --git a/packages/material-ui/src/MenuList/MenuList.d.ts b/packages/material-ui/src/MenuList/MenuList.d.ts
--- a/packages/material-ui/src/MenuList/MenuList.d.ts
+++ b/packages/material-ui/src/MenuList/MenuList.d.ts
@@ -2,10 +2,11 @@ import * as React from 'react';
 import { StandardProps } from '..';
 import { ListProps, ListClassKey } from '../List';
 
-export interface MenuListProps extends StandardProps<ListProps, MenuListClassKey, 'onKeyDown'> {
+export interface MenuListProps extends StandardProps<ListProps, MenuListClassKey> {
   autoFocus?: boolean;
+  autoFocusItem?: boolean;
   disableListWrap?: boolean;
-  onKeyDown?: React.ReactEventHandler<React.KeyboardEvent<any>>;
+  variant?: 'menu' | 'selectedMenu';
 }
 
 export type MenuListClassKey = ListClassKey;
diff --git a/packages/material-ui/src/MenuList/MenuList.js b/packages/material-ui/src/MenuList/MenuList.js
--- a/packages/material-ui/src/MenuList/MenuList.js
+++ b/packages/material-ui/src/MenuList/MenuList.js
@@ -79,13 +79,22 @@ function moveFocus(list, currentFocus, disableListWrap, traversalFunction, textC
 
 const useEnhancedEffect = typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect;
 
+/**
+ * A permanently displayed menu following https://www.w3.org/TR/wai-aria-practices/#menubutton
+ * It's exposed to help customization of the [`Menu`](/api/menu/) component. If you
+ * use it separately you need to move focus into the component manually. Once
+ * the focus is placed inside the component it is fully keyboard accessible.
+ */
 const MenuList = React.forwardRef(function MenuList(props, ref) {
   const {
     actions,
     autoFocus = false,
+    autoFocusItem = false,
+    children,
     className,
     onKeyDown,
     disableListWrap = false,
+    variant = 'selectedMenu',
     ...other
   } = props;
   const listRef = React.useRef(null);
@@ -184,6 +193,58 @@ const MenuList = React.forwardRef(function MenuList(props, ref) {
   }, []);
   const handleRef = useForkRef(handleOwnRef, ref);
 
+  /**
+   * the index of the item should receive focus
+   * in a `variant="selectedMenu"` it's the first `selected` item
+   * otherwise it's the very first item.
+   */
+  let activeItemIndex = -1;
+  // since we inject focus related props into children we have to do a lookahead
+  // to check if there is a `selected` item. We're looking for the last `selected`
+  // item and use the first valid item as a fallback
+  React.Children.forEach(children, (child, index) => {
+    if (!React.isValidElement(child)) {
+      return;
+    }
+
+    if (process.env.NODE_ENV !== 'production') {
+      if (child.type === React.Fragment) {
+        console.error(
+          [
+            "Material-UI: the Menu component doesn't accept a Fragment as a child.",
+            'Consider providing an array instead.',
+          ].join('\n'),
+        );
+      }
+    }
+
+    if (!child.props.disabled) {
+      if (variant === 'selectedMenu' && child.props.selected) {
+        activeItemIndex = index;
+      } else if (activeItemIndex === -1) {
+        activeItemIndex = index;
+      }
+    }
+  });
+
+  const items = React.Children.map(children, (child, index) => {
+    if (index === activeItemIndex) {
+      const newChildProps = {};
+      if (autoFocusItem) {
+        newChildProps.autoFocus = true;
+      }
+      if (child.props.tabIndex === undefined && variant === 'selectedMenu') {
+        newChildProps.tabIndex = 0;
+      }
+
+      if (newChildProps !== null) {
+        return React.cloneElement(child, newChildProps);
+      }
+    }
+
+    return child;
+  });
+
   return (
     <List
       role="menu"
@@ -192,7 +253,9 @@ const MenuList = React.forwardRef(function MenuList(props, ref) {
       onKeyDown={handleKeyDown}
       tabIndex={autoFocus ? 0 : -1}
       {...other}
-    />
+    >
+      {items}
+    </List>
   );
 });
 
@@ -202,10 +265,14 @@ MenuList.propTypes = {
    */
   actions: PropTypes.shape({ current: PropTypes.object }),
   /**
-   * If `true`, the list will be focused during the first mount.
-   * Focus will also be triggered if the value changes from false to true.
+   * If `true`, will focus the `[role="menu"]` container and move into tab order
    */
   autoFocus: PropTypes.bool,
+  /**
+   * If `true`, will focus the first menuitem if `variant="menu"` or selected item
+   * if `variant="selectedMenu"`
+   */
+  autoFocusItem: PropTypes.bool,
   /**
    * MenuList contents, normally `MenuItem`s.
    */
@@ -222,6 +289,11 @@ MenuList.propTypes = {
    * @ignore
    */
   onKeyDown: PropTypes.func,
+  /**
+   * The variant to use. Use `menu` to prevent selected items from impacting the initial focus
+   * and the vertical alignment relative to the anchor element.
+   */
+  variant: PropTypes.oneOf(['menu', 'selectedMenu']),
 };
 
 export default MenuList;
diff --git a/tslint.json b/tslint.json
--- a/tslint.json
+++ b/tslint.json
@@ -5,6 +5,7 @@
     "rules": {
         "deprecation": true,
         "file-name-casing": false,
+        "no-boolean-literal-compare": false,
         "no-empty-interface": false,
         "no-unnecessary-callback-wrapper": false,
         "no-unnecessary-generics": false,


Test patch:
diff --git a/packages/material-ui/src/MenuList/MenuList.test.js b/packages/material-ui/src/MenuList/MenuList.test.js
--- a/packages/material-ui/src/MenuList/MenuList.test.js
+++ b/packages/material-ui/src/MenuList/MenuList.test.js
@@ -1,8 +1,9 @@
 import React from 'react';
-import { assert } from 'chai';
+import { expect } from 'chai';
 import { stub } from 'sinon';
 import { createMount } from '@material-ui/core/test-utils';
 import describeConformance from '@material-ui/core/test-utils/describeConformance';
+import { createClientRender } from 'test/utils/createClientRender';
 import MenuList from './MenuList';
 import getScrollbarSize from '../utils/getScrollbarSize';
 import List from '../List';
@@ -19,33 +20,32 @@ function setStyleWidthForJsdomOrBrowser(style, width) {
 
 describe('<MenuList />', () => {
   let mount;
+  const render = createClientRender({ strict: true });
 
   before(() => {
     mount = createMount({ strict: true });
   });
 
-  after(() => {
-    mount.cleanUp();
-  });
-
   describeConformance(<MenuList />, () => ({
     classes: {},
     inheritComponent: List,
     mount,
     refInstanceof: window.HTMLUListElement,
     skip: ['componentProp'],
+    after: () => mount.cleanUp(),
   }));
 
   describe('prop: children', () => {
-    it('should support invalid children', () => {
-      const wrapper = mount(
+    it('should support null children', () => {
+      const { getAllByRole } = render(
         <MenuList>
-          <div />
-          <div />
+          <div role="menuitem" />
+          <div role="menuitem" />
           {null}
         </MenuList>,
       );
-      assert.strictEqual(wrapper.find('div').length, 2);
+
+      expect(getAllByRole('menuitem')).to.have.length(2);
     });
   });
 
@@ -55,91 +55,87 @@ describe('<MenuList />', () => {
     it('should not adjust style when container element height is greater', () => {
       const menuListActionsRef = React.createRef();
       const listRef = React.createRef();
-      mount(
-        <React.Fragment>
-          <MenuList ref={listRef} actions={menuListActionsRef} />
-        </React.Fragment>,
-      );
+      render(<MenuList ref={listRef} actions={menuListActionsRef} />);
       const list = listRef.current;
-      assert.strictEqual(list.style.paddingRight, '');
-      assert.strictEqual(list.style.paddingLeft, '');
-      assert.strictEqual(list.style.width, '');
+
+      expect(list.style).to.have.property('paddingRight', '');
+      expect(list.style).to.have.property('paddingLeft', '');
+      expect(list.style).to.have.property('width', '');
+
       menuListActionsRef.current.adjustStyleForScrollbar(
         { clientHeight: 20 },
         { direction: 'ltr' },
       );
-      assert.strictEqual(list.style.paddingRight, '');
-      assert.strictEqual(list.style.paddingLeft, '');
-      assert.strictEqual(list.style.width, '');
+
+      expect(list.style).to.have.property('paddingRight', '');
+      expect(list.style).to.have.property('paddingLeft', '');
+      expect(list.style).to.have.property('width', '');
     });
 
     it('should adjust style when container element height is less', () => {
       const menuListActionsRef = React.createRef();
       const listRef = React.createRef();
-      mount(
-        <React.Fragment>
-          <MenuList ref={listRef} actions={menuListActionsRef} />
-        </React.Fragment>,
-      );
+      render(<MenuList ref={listRef} actions={menuListActionsRef} />);
       const list = listRef.current;
       setStyleWidthForJsdomOrBrowser(list.style, '');
       stub(list, 'clientHeight').get(() => 11);
-      assert.strictEqual(list.style.paddingRight, '');
-      assert.strictEqual(list.style.paddingLeft, '');
-      assert.strictEqual(list.style.width, '');
+
+      expect(list.style).to.have.property('paddingRight', '');
+      expect(list.style).to.have.property('paddingLeft', '');
+      expect(list.style).to.have.property('width', '');
+
       menuListActionsRef.current.adjustStyleForScrollbar(
         { clientHeight: 10 },
         { direction: 'ltr' },
       );
-      assert.strictEqual(list.style.paddingRight, expectedPadding);
-      assert.strictEqual(list.style.paddingLeft, '');
-      assert.strictEqual(list.style.width, `calc(100% + ${expectedPadding})`);
+
+      expect(list.style).to.have.property('paddingRight', expectedPadding);
+      expect(list.style).to.have.property('paddingLeft', '');
+      expect(list.style).to.have.property('width', `calc(100% + ${expectedPadding})`);
     });
 
     it('should adjust paddingLeft when direction=rtl', () => {
       const menuListActionsRef = React.createRef();
       const listRef = React.createRef();
-      mount(
-        <React.Fragment>
-          <MenuList ref={listRef} actions={menuListActionsRef} />
-        </React.Fragment>,
-      );
+      render(<MenuList ref={listRef} actions={menuListActionsRef} />);
       const list = listRef.current;
       setStyleWidthForJsdomOrBrowser(list.style, '');
       stub(list, 'clientHeight').get(() => 11);
-      assert.strictEqual(list.style.paddingRight, '');
-      assert.strictEqual(list.style.paddingLeft, '');
-      assert.strictEqual(list.style.width, '');
+
+      expect(list.style).to.have.property('paddingRight', '');
+      expect(list.style).to.have.property('paddingLeft', '');
+      expect(list.style).to.have.property('width', '');
+
       menuListActionsRef.current.adjustStyleForScrollbar(
         { clientHeight: 10 },
         { direction: 'rtl' },
       );
-      assert.strictEqual(list.style.paddingRight, '');
-      assert.strictEqual(list.style.paddingLeft, expectedPadding);
-      assert.strictEqual(list.style.width, `calc(100% + ${expectedPadding})`);
+
+      expect(list.style).to.have.property('paddingRight', '');
+      expect(list.style).to.have.property('paddingLeft', expectedPadding);
+      expect(list.style).to.have.property('width', `calc(100% + ${expectedPadding})`);
     });
 
     it('should not adjust styles when width already specified', () => {
       const menuListActionsRef = React.createRef();
       const listRef = React.createRef();
-      mount(
-        <React.Fragment>
-          <MenuList ref={listRef} actions={menuListActionsRef} />
-        </React.Fragment>,
-      );
+      mount(<MenuList ref={listRef} actions={menuListActionsRef} />);
       const list = listRef.current;
       setStyleWidthForJsdomOrBrowser(list.style, '10px');
       Object.defineProperty(list, 'clientHeight', { value: 11 });
-      assert.strictEqual(list.style.paddingRight, '');
-      assert.strictEqual(list.style.paddingLeft, '');
-      assert.strictEqual(list.style.width, '10px');
+
+      expect(list.style).to.have.property('paddingRight', '');
+      expect(list.style).to.have.property('paddingLeft', '');
+      expect(list.style).to.have.property('width', '10px');
+
       menuListActionsRef.current.adjustStyleForScrollbar(
         { clientHeight: 10 },
         { direction: 'rtl' },
       );
-      assert.strictEqual(list.style.paddingRight, '');
-      assert.strictEqual(list.style.paddingLeft, '');
-      assert.strictEqual(list.style.width, '10px');
+
+      expect(list.style).to.have.property('paddingRight', '');
+      expect(list.style).to.have.property('paddingLeft', '');
+      expect(list.style).to.have.property('width', '10px');
     });
   });
 });
diff --git a/packages/material-ui/test/integration/MenuList.test.js b/packages/material-ui/test/integration/MenuList.test.js
--- a/packages/material-ui/test/integration/MenuList.test.js
+++ b/packages/material-ui/test/integration/MenuList.test.js
@@ -48,10 +48,8 @@ describe('<MenuList> integration', () => {
 
     it('focuses the specified item on mount', () => {
       const { getAllByRole } = render(
-        <MenuList>
-          <MenuItem autoFocus tabIndex={0}>
-            Menu Item 1
-          </MenuItem>
+        <MenuList autoFocusItem>
+          <MenuItem>Menu Item 1</MenuItem>
           <MenuItem>Menu Item 2</MenuItem>
           <MenuItem>Menu Item 3</MenuItem>
         </MenuList>,
@@ -62,10 +60,8 @@ describe('<MenuList> integration', () => {
 
     it('should select the last item when pressing up if the first item is focused', () => {
       const { getAllByRole } = render(
-        <MenuList>
-          <MenuItem autoFocus tabIndex={0}>
-            Menu Item 1
-          </MenuItem>
+        <MenuList autoFocusItem>
+          <MenuItem selected>Menu Item 1</MenuItem>
           <MenuItem>Menu Item 2</MenuItem>
           <MenuItem>Menu Item 3</MenuItem>
         </MenuList>,
@@ -81,10 +77,8 @@ describe('<MenuList> integration', () => {
 
     it('should select the secont item when pressing down if the first item is selected', () => {
       const { getAllByRole } = render(
-        <MenuList>
-          <MenuItem autoFocus tabIndex={0}>
-            Menu Item 1
-          </MenuItem>
+        <MenuList autoFocusItem>
+          <MenuItem selected>Menu Item 1</MenuItem>
           <MenuItem>Menu Item 2</MenuItem>
           <MenuItem>Menu Item 3</MenuItem>
         </MenuList>,
@@ -100,10 +94,8 @@ describe('<MenuList> integration', () => {
 
     it('should still be focused and focusable when going back and forth', () => {
       const { getAllByRole } = render(
-        <MenuList>
-          <MenuItem autoFocus tabIndex={0}>
-            Menu Item 1
-          </MenuItem>
+        <MenuList autoFocusItem>
+          <MenuItem selected>Menu Item 1</MenuItem>
           <MenuItem>Menu Item 2</MenuItem>
           <MenuItem>Menu Item 3</MenuItem>
         </MenuList>,
@@ -121,10 +113,8 @@ describe('<MenuList> integration', () => {
     it('should leave tabIndex on the first item after blur', () => {
       const handleBlur = spy();
       const { getAllByRole } = render(
-        <MenuList onBlur={handleBlur}>
-          <MenuItem autoFocus tabIndex={0}>
-            Menu Item 1
-          </MenuItem>
+        <MenuList autoFocusItem onBlur={handleBlur}>
+          <MenuItem selected>Menu Item 1</MenuItem>
           <MenuItem>Menu Item 2</MenuItem>
           <MenuItem>Menu Item 3</MenuItem>
         </MenuList>,
@@ -144,8 +134,8 @@ describe('<MenuList> integration', () => {
 
     it('can imperatively focus the first item', () => {
       const { getAllByRole } = render(
-        <MenuList>
-          <MenuItem tabIndex={0}>Menu Item 1</MenuItem>
+        <MenuList autoFocusItem>
+          <MenuItem selected>Menu Item 1</MenuItem>
           <MenuItem>Menu Item 2</MenuItem>
           <MenuItem>Menu Item 3</MenuItem>
         </MenuList>,
@@ -161,10 +151,8 @@ describe('<MenuList> integration', () => {
 
     it('down arrow can go to all items while not changing tabIndex', () => {
       const { getAllByRole } = render(
-        <MenuList>
-          <MenuItem autoFocus tabIndex={0}>
-            Menu Item 1
-          </MenuItem>
+        <MenuList autoFocusItem>
+          <MenuItem selected>Menu Item 1</MenuItem>
           <MenuItem>Menu Item 2</MenuItem>
           <MenuItem>Menu Item 3</MenuItem>
         </MenuList>,
@@ -229,9 +217,7 @@ describe('<MenuList> integration', () => {
       const { getAllByRole } = render(
         <MenuList autoFocus>
           <MenuItem>Menu Item 1</MenuItem>
-          <MenuItem selected tabIndex={0}>
-            Menu Item 2
-          </MenuItem>
+          <MenuItem selected>Menu Item 2</MenuItem>
           <MenuItem>Menu Item 3</MenuItem>
         </MenuList>,
       );
@@ -264,38 +250,31 @@ describe('<MenuList> integration', () => {
     });
   });
 
-  specify('menuitems can focus themselves imperatively', () => {
-    function FocusOnMountMenuItem(props) {
-      const listItemRef = React.useRef(null);
-      React.useLayoutEffect(() => {
-        listItemRef.current.focus();
-      }, []);
-      return <MenuItem {...props} ref={listItemRef} tabIndex={0} />;
-    }
-    const { getAllByRole } = render(
-      <MenuList>
-        <MenuItem>Menu Item 1</MenuItem>
-        <MenuItem>Menu Item 2</MenuItem>
-        <FocusOnMountMenuItem>Menu Item 3</FocusOnMountMenuItem>
-        <MenuItem>Menu Item 4</MenuItem>
-      </MenuList>,
-    );
+  specify(
+    'initial focus is controlled by setting the selected prop when `autoFocusItem` is enabled',
+    () => {
+      const { getAllByRole } = render(
+        <MenuList autoFocusItem>
+          <MenuItem>Menu Item 1</MenuItem>
+          <MenuItem>Menu Item 2</MenuItem>
+          <MenuItem selected>Menu Item 3</MenuItem>
+          <MenuItem>Menu Item 4</MenuItem>
+        </MenuList>,
+      );
 
-    expect(getAllByRole('menuitem')[2]).to.be.focused;
-    expect(getAllByRole('menuitem')[0]).to.have.property('tabIndex', -1);
-    expect(getAllByRole('menuitem')[1]).to.have.property('tabIndex', -1);
-    expect(getAllByRole('menuitem')[2]).to.have.property('tabIndex', 0);
-    expect(getAllByRole('menuitem')[3]).to.have.property('tabIndex', -1);
-  });
+      expect(getAllByRole('menuitem')[2]).to.be.focused;
+      expect(getAllByRole('menuitem')[0]).to.have.property('tabIndex', -1);
+      expect(getAllByRole('menuitem')[1]).to.have.property('tabIndex', -1);
+      expect(getAllByRole('menuitem')[2]).to.have.property('tabIndex', 0);
+      expect(getAllByRole('menuitem')[3]).to.have.property('tabIndex', -1);
+    },
+  );
 
-  // broken
   describe('MenuList with disableListWrap', () => {
     it('should not wrap focus with ArrowUp from first', () => {
       const { getAllByRole } = render(
-        <MenuList disableListWrap>
-          <MenuItem autoFocus tabIndex={0}>
-            Menu Item 1
-          </MenuItem>
+        <MenuList autoFocusItem disableListWrap>
+          <MenuItem selected>Menu Item 1</MenuItem>
           <MenuItem>Menu Item 2</MenuItem>
         </MenuList>,
       );
@@ -309,11 +288,9 @@ describe('<MenuList> integration', () => {
 
     it('should not wrap focus with ArrowDown from last', () => {
       const { getAllByRole } = render(
-        <MenuList disableListWrap>
+        <MenuList autoFocusItem disableListWrap>
           <MenuItem>Menu Item 1</MenuItem>
-          <MenuItem autoFocus tabIndex={0}>
-            Menu Item 2
-          </MenuItem>
+          <MenuItem selected>Menu Item 2</MenuItem>
         </MenuList>,
       );