Skip to main content
react-md
react-md - Menu - Demos

Simple Examples

Menus within react-md can be created using the DropdownMenu component which is a wrapper for the lower level components and should work for most of your use cases. The menu will handle the visibility of the menu and renders the button to show the menu, the menu itself, and all items within the menu.

All the props provided to the DropdownMenu will be passed down to the Button component so you can style your button like normal. To render dropdown items, you must supply a list of items to the dropdown menu which will be rendered in MenuItems with the following logic by default:

  • if it is false-ish, render null (so nothing)
  • if it is the string "separator", render a MenuItemSeparator
  • if it is an object that has role="separator", render a MenuItemSeparator and pass the object as props.
  • if it is a string, number, render it as the children of the MenuItem component.
  • if it is a valid React element, return it instead.
  • if it is an object and the href, to, or component props exist, render it as a MenuItemLink
  • if it is an object, render it as the props for the MenuItem

Note that links must be rendered with the MenuItemLink component instead of MenuItem. The MenuItemLink updates the HTML structure a bit to be accessible and still ensure the link is focusable.

In addition, the MenuButton will render with a dropdown icon by default if the buttonType !== "icon", but this can be disabled or changed with the disableDropdownIcon and dropdownIcon props.

Adding Event Handlers

You can add event handlers to items in multiple ways:

Adding an onClick to each item

Updating each item to be an object with an onClick attribute might be the easiest way to add an event handler. An example would be:

12345678910111213141516171819-const items = ["Item 1", "Item 2", "Item 3"];
+const items = [
+  {
+    children: "Item 1",
+    onClick: () => console.log("Clicked Item 1"),
+  },
+  {
+    children: "Item 2",
+    onClick: () => console.log("Clicked Item 2"),
+  },
+  {
+    children: "Item 3",
+    onClick: () => console.log("Clicked Item 3"),
+  },
+  {
+    children: "Item 4",
+    onClick: () => console.log("Clicked Item 4"),
+  },
+]
Using custom MenuItem

The most "reusable" option will be to create custom MenuItem and/or MenuItemLink components that have their own click handlers attached. This will also make it so you can have reusable MenuItems if they need to appear in multiple menus or need to make them have additional functionality (like connecting to redux actions). You'll just want to import the MenuItem component and add it to the items list like normal. The default item renderer will automatically clone each item with a unique key as well, so you won't need to manually define keys yourself.

Adding onClick

Last clicked value: None

Custom MenuItem

Last clicked value: None

Fixing Overflow Issues

After reading over a couple of the other examples, you might be wondering why all this work is going into positioning menus since this seems a bit overkill. If you've made a pop-out menu before, you'll know that you can pretty easily position two elements together with:

123456789101112.container {
  display: inline-block;
  position: relative;
}

.child {
  position: absolute;

  // or whatever positioning options you'd like
  right: 0;
  top: 0;
}
1234<div class="container">
  <button type="button">Button</button>
  <div class="child" role="menu">Content</div>
</div>

This works for simple menus but has a few drawbacks:

  • if there's any overflow set for a parent element in the DOM, the menu will only be visible within that overflow container.
  • you'll need to know the height and width of both the container and menu as well as the position the container element is within the viewport to be able to determine if the menu can be shown.

To work around these issues, the Menu component is rendered using position: fixed and all that additional positioning logic goes on behind the scenes to handle everything for you to ensure that your menu is visible within the viewport. This is also great just in-case you want to be able to portal (@react-md/portal) the menu as well.

This example below will use the position: relative approach to show these problems as well as the DropdownMenu implementation that fixes them.

Accessibility Example

Menus within react-md are fully accessible for screen readers and keyboard users. The menu can be opened in a few different ways:

  • clicking on the menu button via mouse, touch, or keyboard (space or enter)
  • using the up or down arrow keys

If the menu was opened with an up arrow key, the last item in the menu will be focused initially while all the other event types will focus the first item. While the menu is open, the user can navigate through the items by:

  • using the up and down arrow keys
  • using the end and home keys
  • typing the letters for one of the menu items

The menu will also automatically have an aria-labelledby="BUTTON_ID" by default since the MenuButton normally describes the menu. If this is not the case, you can provide your own menuLabel or menuLabelledBy props to correctly label your menu.

Just a reminder, if you render an icon MenuButton, you'll need to correctly provide an accessible label via aria-label, aria-labelledby, or add a screen reader visible only text within the button.

Simple Context Menu

Custom context menus can be created within react-md by using the useContextMenu hook along with the Menu component. The useContextMenu hook will maintain the same positioning logic so that it will attempt to render itself within the viewport and update its position to be relative to the pointer instead of the container element. It also returns the following ordered list:

  • an object of props to provide to the Menu component to handle its visibility, a default id, and positioning options so the menu is rendered from the pointer location when needed
  • a onContextMenu handler that should be added to the element that triggers a custom context menu
  • a React setState function for manually toggling the visibility of the menu yourself (probably won't be used much).

The hook has some sensible defaults, but it's possible to provide your own configuration object that has a custom id for the menu, a positioning anchor, as well as custom classNames for the menu transition. This object also allows for a custom ref that will be merged with the returned ref from this hook if you need access to the Menu's DOM node.

Just like normal DropdownMenu and Menu components, the context menu is fully keyboard navigable and will return focus to the focusable element that triggered the context menu when it is closed since it is possible to open a context menu with the special context menu key for Windows and Linux operating systems. Mac does not have a similar way to trigger context menus from the keyboard, but it's possible to enable custom accessibility options to create a context menu from the current pointer location.

The example below will implement some custom context menus for each content item within the fake Google drive with zero functionality.

NameOwnerLast ModifiedSize
Documentsme2020-01-03T08:00:00.000Z-
My Picturesme2019-12-29T00:00:00.000Z-
Amazing Picture.jpgme2019-12-29T00:00:00.000Z32423
Look_at_This.pngme2019-12-20T03:02:00.000Z2343
Some band live.movme2019-12-29T00:00:00.000Z90823490
My Movie.mp4me2019-12-29T00:00:00.000Z90823490

Horizontal Menu

Menus can also be rendered horizontally by enabling the horizontal prop. This will automatically update the keyboard behavior so that the ArrowLeft and ArrowRight keys will be used to navigate the menu items instead of the ArrowUp and ArrowDown.

Nested Dropdown Menus

This package also supports creating nested menu items with the same accessibility features as a single level menu using the DropdownMenuItem component. These sorts of menus are really only used for complex desktop applications where everything can't be done in a single menu (like Google Docs).

To create the nested menus, all you'll need to do is update the items prop in the main DropdownMenu component to include you DropdownMenuItem that also defines its own items to render. When you use this component, it'll basically be the same as the DropdownMenu except it'll render as a MenuItem and add a few small changes:

  1. The menu can now be opened with the ArrowLeft and ArrowRight keys which will have the same behavior as the ArrowUp and ArrowDown keys respectively. So pressing the ArrowLeft key will open the menu and focus the last menu item by default while ArrowRight will open the menu and focus the first menu item
  2. The menu can now also be closed by using the ArrowLeft key if the MenuItem that is currently focused is not another nested menu item.
  3. The role of the MenuItem is updated to be "button" instead of "menuitem" for the required accessibility changes
  4. To fix a Safari bug, the nested menus will be portalled.

The nested menus will be portalled out of the menu due to a weird visual bug that occurs within Safari. For some reason, if you have overflow set along with a parent that has position set to something other than static, the position: fixed element will not be visible. To work around this, the nested menus portal out of the parent menu so they can be displayed. Funnily enough, it is still rendered and you can click it, but you just can't see anything.

The example below will create an infinitely generating dropdown menu example that is actually kind of fun to see how the positioning and cascading works. If you'd like to see the Safari example, you can also toggle the checkbox to disable the portal behavior.

Note: if you end up creating a bit more than 7-10 nested dropdowns, it'll take a bit for the menus to close when you click outside of this example. You should probably never create such a bad interface/so many nested dropdowns in a real world app though.

Custom Renderers

It is possible to also use a custom renderer if you need more control over the menu or menu item generation using the menuRenderer and/or itemRenderer props. The menuRenderer will provide the base Menu props as the first argument as well as the items list as the second argument. The itemRenderer will provide the current item and the key to use for the item.

A great example for this would be using a library like react-virtualized to render a giant list of items. When virtualizing the menu's list you will gain a giant performance boost in the mounting and unmounting of the menu, but the built in keyboard accessibility will be broken.

The keyboard movement and focus behavior only works for items that are currently rendered in the DOM. Since it is now a virtual list, you will be unable to:

  • use the arrow keys to focus wrap to the top and bottom of the menu
  • use the home or end keys to focus the first or last item in the menu
  • type letters to match an item in the menu.

This example below will show a non-virtualized and virtualized example to show the performance differences between the two and some drawbacks/limitations to the custom renderer.

Last clicked value: None

Last clicked value: None