Menu
A menu displays a list of choices on a temporary surface. They appear when users interact with a button, action, or other control.
Simple Example
A menu can be created by using the DropdownMenu and MenuItem components. The
DropdownMenu requires the buttonChildren prop to render text, icons, or
other content in a button and the available MenuItems as children.
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { type ReactElement } from "react";
export default function SimpleExample(): ReactElement {
return (
<DropdownMenu buttonChildren="Dropdown">
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
);
}
Menu Item Actions
MenuItems should normally provide an onClick event handler
The last clicked item is: none
"use client";
import { Box } from "@react-md/core/box/Box";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement, useState } from "react";
export default function MenuitemActionExample(): ReactElement {
const [lastClicked, setLastClicked] = useState("");
return (
<Box stacked align="start">
<DropdownMenu buttonChildren="Dropdown">
<MenuItem
onClick={() => {
setLastClicked("Item 1");
}}
>
Item 1
</MenuItem>
<MenuItem
onClick={() => {
setLastClicked("Item 2");
}}
>
Item 2
</MenuItem>
<MenuItem
onClick={() => {
setLastClicked("Item 3");
}}
>
Item 3
</MenuItem>
</DropdownMenu>
<Typography>
The last clicked item is: <code>{lastClicked || "none"}</code>
</Typography>
</Box>
);
}
Horizontal Menu
A menu can be rendered horizontally instead of vertically by enabling the
horizontal prop.
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { type ReactElement } from "react";
export default function HorizontalMenuExample(): ReactElement {
return (
<DropdownMenu buttonChildren="Dropdown" horizontal>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
);
}
Button Styling
Most of the Button props are available as top-level props
on the DropdownMenu.
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import MoreVertOutlinedIcon from "@react-md/material-icons/MoreVertOutlinedIcon";
import { type ReactElement } from "react";
export default function ButtonStyling(): ReactElement {
return (
<>
<DropdownMenu
theme="primary"
themeType="outline"
buttonChildren="Options..."
>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
<DropdownMenu
theme="secondary"
themeType="contained"
iconSize="small"
buttonType="icon"
buttonChildren={<MoreVertOutlinedIcon />}
>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
</>
);
}
Icon Button
An icon button can be rendered by setting the buttonType to "icon". This
will also remove the default dropdown icon within the button.
Remember to provide an aria-label or accessible text for screen
readers when using icon buttons.
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import MoreVertOutlinedIcon from "@react-md/material-icons/MoreVertOutlinedIcon";
import { type ReactElement } from "react";
export default function IconButtonExample(): ReactElement {
return (
<DropdownMenu
aria-label="Options"
buttonType="icon"
buttonChildren={<MoreVertOutlinedIcon />}
>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
);
}
Floating Action Button
A menu can be rendered in a floating action button by providing the floating
prop. Just like the Button component, the button will default to an icon
button if the floating prop is defined instead of a text button.
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import MoreVertOutlinedIcon from "@react-md/material-icons/MoreVertOutlinedIcon";
import { type ReactElement } from "react";
export default function FloatingActionButtonExample(): ReactElement {
return (
<>
<DropdownMenu
floating="top-left"
buttonChildren={<MoreVertOutlinedIcon />}
>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
<DropdownMenu
aria-label="Options"
floating="top-right"
buttonType="text"
buttonChildren="Options"
>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
</>
);
}
Menu Item Addons
A MenuItem can render addons just like the ListItem.
import { Avatar } from "@react-md/core/avatar/Avatar";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import FavoriteIcon from "@react-md/material-icons/FavoriteIcon";
import { type ReactElement } from "react";
export default function MenuItemAddonsExample(): ReactElement {
return (
<DropdownMenu buttonChildren="Dropdown">
<MenuItem leftAddon={<FavoriteIcon />}>Item 1</MenuItem>
<MenuItem rightAddon={<Avatar>I</Avatar>} rightAddonType="avatar">
Item 1
</MenuItem>
</DropdownMenu>
);
}
Configuring Menu Behavior
The Menu uses the useFixedPositioning hook to position itself
within the viewport. Check out the
useFixedPositioning documentation page for
available options.
Controlling Menu Visibility
If the menu's visibility must manually be controlled, provide the visible and
setVisible props.
import { Button } from "@react-md/core/button/Button";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { type ReactElement, useState } from "react";
export default function ControllingMenuVisibility(): ReactElement {
const [visible, setVisible] = useState(false);
return (
<>
<Button
onClick={() => {
setVisible(true);
}}
>
Open
</Button>
<DropdownMenu
buttonChildren="Options..."
visible={visible}
setVisible={setVisible}
>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
</>
);
}
Prevent Scroll Example
The default behavior is to reposition the Menu while the user scrolls the page
until the menu is no longer in the viewport. Another approach is to prevent the
user from scrolling the page while the menu is open. To enable this behavior,
enable the preventScroll prop.
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { type ReactElement } from "react";
export default function PreventScrollExample(): ReactElement {
return (
<DropdownMenu buttonChildren="Options..." preventScroll>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
);
}
Close On Resize Example
The Menu will automatically reposition itself if the user resizes the browser.
Another approach is to close the menu instead. Enable the closeOnResize prop
to opt into this behavior.
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { type ReactElement } from "react";
export default function CloseOnResizeExample(): ReactElement {
return (
<DropdownMenu buttonChildren="Options..." closeOnResize>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
);
}
Disable Portal Example
The Menu will use the Portal by default to help with
positioning issues within the viewport and temporary elements like
Dialogs. The portal behavior can be disabled by enabling
the disablePortal prop which will make the Menu render as the next element
sibling of the menu toggle button.
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { type ReactElement } from "react";
export default function DisablePortalExample(): ReactElement {
return (
<DropdownMenu buttonChildren="Options..." disablePortal>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
);
}
Disable Conditional Rendering Example
The Menu will only be mounted while visible which means any internal state
is reset each time the Menu is opened. Set the temporary prop to false if
the Menu should remain mounted while not visible and be hidden using CSS
instead.
"use client";
import { ListItem } from "@react-md/core/list/ListItem";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { type ReactElement, useState } from "react";
export default function DisableConditionalRenderingExample(): ReactElement {
return (
<DropdownMenu buttonChildren="Options..." temporary={false}>
<ExampleChildren />
</DropdownMenu>
);
}
function ExampleChildren(): ReactElement {
const [clicked, setClicked] = useState(-1);
return (
<>
<ListItem presentational>{`Last clicked: ${clicked}`}</ListItem>
<MenuItem
onClick={() => {
setClicked(0);
}}
>
Item 1
</MenuItem>
<MenuItem
onClick={() => {
setClicked(1);
}}
>
Item 2
</MenuItem>
<MenuItem
onClick={() => {
setClicked(2);
}}
>
Item 3
</MenuItem>
</>
);
}
Context Menu
To override the default right click behavior and display a custom context menu,
use the useContextMenu hook and the Menu component. The useContextMenu
will provide the required props for the Menu component and automatically
position it relative to the mouse.
The context menu will also update the menu to have the following default props:
anchor-BELOW_INNER_LEFT_ANCHORmenuLabel-"Context Menu"preventScroll-true
"use client";
import { TextArea } from "@react-md/core/form/TextArea";
import {
// BELOW_INNER_LEFT_ANCHOR,
Menu,
} from "@react-md/core/menu/Menu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { useContextMenu } from "@react-md/core/menu/useContextMenu";
import ContentCopyOutlinedIcon from "@react-md/material-icons/ContentCopyOutlinedIcon";
import ContentCutOutlinedIcon from "@react-md/material-icons/ContentCutOutlinedIcon";
import ContentPasteOutlinedIcon from "@react-md/material-icons/ContentPasteOutlinedIcon";
import { type ReactElement } from "react";
export default function ContextMenuExample(): ReactElement {
const { menuProps, onContextMenu } = useContextMenu();
// const { menuProps, onContextMenu } = useContextMenu({
// // these are the default values
// anchor: BELOW_INNER_LEFT_ANCHOR,
// menuLabel: "Context Menu",
// preventScroll: true,
//
// // do something custom with the context menu event
// onContextMenu(event) {
// },
// });
return (
<>
<TextArea onContextMenu={onContextMenu} placeholder="Right click me!" />
<Menu {...menuProps}>
<MenuItem leftAddon={<ContentCutOutlinedIcon />}>Cut</MenuItem>
<MenuItem leftAddon={<ContentCopyOutlinedIcon />}>Copy</MenuItem>
<MenuItem leftAddon={<ContentPasteOutlinedIcon />}>Paste</MenuItem>
</Menu>
</>
);
}
Nested Menus
Nested dropdown menus can be created by adding a DropdownMenu as a child of
another DropdownMenu. The DropdownMenu will render as a MenuItemButton
instead of as a MenuButton and allow for all the correct keyboard behavior.
import { Box } from "@react-md/core/box/Box";
import { Switch } from "@react-md/core/form/Switch";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { type ReactElement, useState } from "react";
export default function NestedMenusExample(): ReactElement {
const [horizontal, setHorizontal] = useState(false);
return (
<Box stacked>
<Switch
label="Horizontal?"
checked={horizontal}
onChange={(event) => {
setHorizontal(event.currentTarget.checked);
}}
/>
<DropdownMenu buttonChildren="Dropdown" horizontal={horizontal}>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<DropdownMenu buttonChildren="Item 3">
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
<MenuItem>Item 4</MenuItem>
<MenuItem>Item 5</MenuItem>
</DropdownMenu>
</Box>
);
}
Rendering in a Sheet
It can sometimes be useful to render a Menu within a Sheet instead of a
temporary popup element when on smaller viewports or dealing with large menu
items.
This can be configured on an app-level using the MenuConfigurationProvider or
a specific DropdownMenu/Menu by providing a renderAsSheet prop. This can
be set to "phone" to only render menus within sheets on the phone app size or
supports true/false values if custom logic is required to render as a sheet
instead.
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuConfigurationProvider } from "@react-md/core/menu/MenuConfigurationProvider";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { type ReactElement } from "react";
export default function RenderingInASheet(): ReactElement {
return (
<>
<MenuConfigurationProvider
renderAsSheet
// renderAsSheet={false}
// renderAsSheet="phone"
>
<DropdownMenu buttonChildren="Dropdown">
<MenuItem disabled>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
<MenuItem>Item 4</MenuItem>
<MenuItem>Item 5</MenuItem>
</DropdownMenu>
</MenuConfigurationProvider>
</>
);
}
Sheet Options
The Sheet can be configured further to support an optional header, an optional
footer, the position within the viewport, and the vertical size. The optional
header or footer components can be updated to control the visibility of the menu
as well by using the useMenuVisibility hook which provides the visible state
and setVisible function.
Check out the Sheet for more sheet behavior and styling.
import { AppBar } from "@react-md/core/app-bar/AppBar";
import { AppBarTitle } from "@react-md/core/app-bar/AppBarTitle";
import { Button } from "@react-md/core/button/Button";
import { DialogFooter } from "@react-md/core/dialog/DialogFooter";
import { Divider } from "@react-md/core/divider/Divider";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuConfigurationProvider } from "@react-md/core/menu/MenuConfigurationProvider";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { useMenuVisibility } from "@react-md/core/menu/MenuVisibilityProvider";
import CloseIcon from "@react-md/material-icons/CloseIcon";
import { type ReactElement } from "react";
export default function SheetOptionsExample(): ReactElement {
const renderAsSheet = true;
return (
<>
<MenuConfigurationProvider
sheetHeader={<Header />}
sheetFooter={<Footer />}
renderAsSheet={renderAsSheet}
// these are the defaults
sheetPosition="bottom"
sheetVerticalSize="touch"
>
<DropdownMenu
buttonChildren="Dropdown"
// additional styles can be passed to the sheet from the DropdownMenu
// sheetStyle={{ margin: "0 2rem" }}
// sheetClassName="my-custom-class-name"
//
// any other props available on the Sheet component
// sheetProps={{
// horizontalSize: "touch"
// }}
>
<MenuItem disabled>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<DropdownMenu buttonChildren="Child Dropdown">
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
<MenuItem>Item 4</MenuItem>
<MenuItem>Item 5</MenuItem>
</DropdownMenu>
<MenuItem>Item 3</MenuItem>
<MenuItem>Item 4</MenuItem>
<MenuItem>Item 5</MenuItem>
</DropdownMenu>
</MenuConfigurationProvider>
</>
);
}
function Header(): ReactElement {
const { setVisible } = useMenuVisibility();
return (
<AppBar theme="clear">
<AppBarTitle>Custom</AppBarTitle>
<Button
onClick={() => {
setVisible(false);
}}
buttonType="icon"
>
<CloseIcon />
</Button>
</AppBar>
);
}
function Footer(): ReactElement {
const { setVisible } = useMenuVisibility();
return (
<>
<Divider />
<DialogFooter>
<Button
onClick={() => {
setVisible(false);
}}
>
Cancel
</Button>
</DialogFooter>
</>
);
}
Hoverable Menu
The DropdownMenu can be updated to become visible after a specific hover
timeout by wrapping a group of dropdown menus within the MenuBar component to
and providing a hoverTimeout prop. When the hoverTimeout is set to 0, the
DropdownMenu will open immediately otherwise the DropdownMenu must be
hovered for the duration in milliseconds.
The MenuBar also provides special keyboard movement behavior so the
children should normally only be DropdownMenu components. Check out the
MenuBar accessibility section for more info around the keyboard
movement behavior.
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuBar } from "@react-md/core/menu/MenuBar";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import type { ReactElement, ReactNode } from "react";
export default function HoverableMenuExample(): ReactElement {
return (
<MenuBar hoverTimeout={0}>
<DropdownMenu buttonChildren="Item 1">
<MenuItem>Menu Item 1</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
<MenuItem>Menu Item 3</MenuItem>
<MenuItem>Menu Item 4</MenuItem>
</DropdownMenu>
<DropdownMenu buttonChildren="Item 2">
<MenuItem>Menu Item 1</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
<MenuItem>Menu Item 3</MenuItem>
<MenuItem>Menu Item 4</MenuItem>
</DropdownMenu>
<DropdownMenu buttonChildren="Item 3">
<MenuItem>Menu Item 1</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
<InfiniteDropdownMenu buttonChildren="Menu Item 3" depth={0} />
<MenuItem>Menu Item 4</MenuItem>
</DropdownMenu>
<DropdownMenu buttonChildren="Item 4">
<MenuItem>Menu Item 1</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
<MenuItem>Menu Item 3</MenuItem>
<MenuItem>Menu Item 4</MenuItem>
</DropdownMenu>
</MenuBar>
);
}
interface InfiniteDropdownMenuProps {
depth: number;
buttonChildren: ReactNode;
}
function InfiniteDropdownMenu(props: InfiniteDropdownMenuProps): ReactElement {
const { depth, buttonChildren } = props;
return (
<DropdownMenu buttonChildren={buttonChildren}>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
{Array.from({ length: 4 }, (_, i) => (
<InfiniteDropdownMenu
key={i}
depth={depth + 1}
buttonChildren={`Item ${i + 1}`}
/>
))}
<MenuItem>Item 7</MenuItem>
</DropdownMenu>
);
}
Click-first Hoverable Menu
The DropdownMenu can also behave like the browser bookmarks toolbar where
once a menu has been opened, all other menus within the group will immediately
open once hovered. Omit the hoverTimeout prop or set it to undefined to use
this functionality.
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuBar } from "@react-md/core/menu/MenuBar";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import type { ReactElement, ReactNode } from "react";
export default function ClickFirstHoverableMenu(): ReactElement {
return (
<MenuBar>
<DropdownMenu buttonChildren="Item 1">
<MenuItem>Menu Item 1</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
<MenuItem>Menu Item 3</MenuItem>
<MenuItem>Menu Item 4</MenuItem>
</DropdownMenu>
<DropdownMenu buttonChildren="Item 2">
<MenuItem>Menu Item 1</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
<MenuItem>Menu Item 3</MenuItem>
<MenuItem>Menu Item 4</MenuItem>
</DropdownMenu>
<DropdownMenu buttonChildren="Item 3">
<MenuItem>Menu Item 1</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
<InfiniteDropdownMenu buttonChildren="Menu Item 3" depth={0} />
<MenuItem>Menu Item 4</MenuItem>
</DropdownMenu>
<DropdownMenu buttonChildren="Item 4">
<MenuItem>Menu Item 1</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
<MenuItem>Menu Item 3</MenuItem>
<MenuItem>Menu Item 4</MenuItem>
</DropdownMenu>
</MenuBar>
);
}
interface InfiniteDropdownMenuProps {
depth: number;
buttonChildren: ReactNode;
}
function InfiniteDropdownMenu(props: InfiniteDropdownMenuProps): ReactElement {
const { depth, buttonChildren } = props;
return (
<DropdownMenu buttonChildren={buttonChildren}>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
{Array.from({ length: 4 }, (_, i) => (
<InfiniteDropdownMenu
key={i}
depth={depth + 1}
buttonChildren={`Item ${i + 1}`}
/>
))}
<MenuItem>Item 7</MenuItem>
</DropdownMenu>
);
}
Additional MenuItem Components
The following components have been provided to merge the functionality and
accessibility into a MenuItem.
MenuItemCheckboxMenuItemRadioMenuItemSwitchMenuItemTextFieldMenuItemFileInputMenuItemSeparatorMenuItemGroup
These components will ensure the correct accessibility and keyboard movement
within a DropdownMenu.
Enable the preventMenuHideOnClick if the menu should not close after
clicking the menu item.
MenuItemCheckbox Example
The MenuItemCheckbox component can be used to render a checkbox within a
MenuItem and is a controlled component requiring the checked and
onCheckedChange props. It is recommended to use the
useCheckboxGroup hook when multiple checkboxes are
involved since it also supports indeterminate checkboxes.
"use client";
import { useCheckboxGroup } from "@react-md/core/form/useCheckboxGroup";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItemCheckbox } from "@react-md/core/menu/MenuItemCheckbox";
import { MenuItemSeparator } from "@react-md/core/menu/MenuItemSeparator";
import { type ReactElement, useState } from "react";
const values = ["a", "b", "c", "d"] as const;
const labels = {
a: "Label 1",
b: "Label 2",
c: "Label 3",
d: "Label 4",
} as const;
export default function MenuItemCheckboxExample(): ReactElement {
const [bold, setBold] = useState(false);
const [italic, setItalic] = useState(false);
const { getCheckboxProps, getIndeterminateProps } = useCheckboxGroup({
values,
menu: true,
});
return (
<DropdownMenu buttonChildren="Checkboxes" themeType="outline">
<MenuItemCheckbox {...getIndeterminateProps()} preventMenuHideOnClick>
Toggle All
</MenuItemCheckbox>
{values.map((value) => (
<MenuItemCheckbox
key={value}
{...getCheckboxProps(value)}
preventMenuHideOnClick
>
{labels[value]}
</MenuItemCheckbox>
))}
<MenuItemSeparator />
<MenuItemCheckbox disabled checked={false} onCheckedChange={() => {}}>
Disabled
</MenuItemCheckbox>
<MenuItemCheckbox
checked={bold}
onCheckedChange={(checked) => {
setBold(checked);
}}
>
Bold
</MenuItemCheckbox>
<MenuItemCheckbox
checked={italic}
onCheckedChange={(checked) => {
setItalic(checked);
}}
>
Italic
</MenuItemCheckbox>
</DropdownMenu>
);
}
MenuItemRadio Example
The MenuItemRadio component can be used to render a radio within a MenuItem
and is a controlled component requiring the checked and onCheckedChange
props. It is recommended to use the useRadioGroup hook
to control the group of radios.
"use client";
import { type TextDecoration } from "@react-md/core/cssUtils";
import { useRadioGroup } from "@react-md/core/form/useRadioGroup";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItemRadio } from "@react-md/core/menu/MenuItemRadio";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement } from "react";
const decorations = [
"none",
"underline",
"overline",
"strike-through",
] as const;
type Decoration = (typeof decorations)[number];
const getDecoration = (decoration: Decoration): TextDecoration | undefined => {
if (decoration === "none") {
return undefined;
}
return decoration === "strike-through" ? "line-through" : decoration;
};
export default function MenuItemRadioExample(): ReactElement {
const { getRadioProps } = useRadioGroup<Decoration>({
menu: true,
defaultValue: "none",
});
return (
<DropdownMenu buttonChildren="Radio" themeType="outline">
{decorations.map((decoration) => (
<MenuItemRadio
key={decoration}
{...getRadioProps(decoration)}
// preventMenuHideOnClick
>
<Typography textDecoration={getDecoration(decoration)} as="span">
{decoration}
</Typography>
</MenuItemRadio>
))}
<MenuItemRadio disabled checked={false} onCheckedChange={() => {}}>
Disabled
</MenuItemRadio>
</DropdownMenu>
);
}
MenuItemSwitch Example
The MenuItemSwitch component can be used to render a Switch within a
MenuItem and is a controlled component requiring the checked and
onCheckedChange props. The state can be controlled by the
useCheckboxGroup if desired.
"use client";
import { useCheckboxGroup } from "@react-md/core/form/useCheckboxGroup";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItemSwitch } from "@react-md/core/menu/MenuItemSwitch";
import { type ReactElement, useState } from "react";
export default function MenuItemSwitchExample(): ReactElement {
const { getCheckboxProps } = useCheckboxGroup({ menu: true });
const [checked, setChecked] = useState(false);
return (
<DropdownMenu buttonChildren="Dropdown" themeType="outline">
<MenuItemSwitch
checked={checked}
onCheckedChange={(checked) => {
setChecked(checked);
}}
preventMenuHideOnClick
>
Label
</MenuItemSwitch>
<MenuItemSwitch {...getCheckboxProps("0")}>Label</MenuItemSwitch>
<MenuItemSwitch iconAfter {...getCheckboxProps("1")}>
Label
</MenuItemSwitch>
</DropdownMenu>
);
}
MenuItemTextField Example
The MenuItemTextField can be used to render a TextField within a MenuItem.
This component will update the menu keyboard behavior so that the menu keyboard
movement only occurs when there is no value within the text field.
Like the normal TextField, this component defaults to uncontrolled but can be
controlled by providing the value and onChange props.
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { MenuItemSeparator } from "@react-md/core/menu/MenuItemSeparator";
import { MenuItemTextField } from "@react-md/core/menu/MenuItemTextField";
import SearchIcon from "@react-md/material-icons/SearchIcon";
import { type ReactElement } from "react";
export default function MenuItemTextFieldExample(): ReactElement {
return (
<DropdownMenu buttonChildren="Dropdown" themeType="outline">
<MenuItemTextField
aria-label="Search"
placeholder="Search"
rightAddon={<SearchIcon />}
/>
<MenuItemSeparator />
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
<MenuItem>Item 4</MenuItem>
</DropdownMenu>
);
}
MenuItemFileInput Example
The MenuItemFileInput can be used to render FileInput components within a
MenuItem. The main difference is that the upload icon will be rendered as a
MenuItem addon and a label should be provided within the MenuItemFileInput
children.
This component works with the useFileUpload hook and should be used for more complex file selection.
import { useFileUpload } from "@react-md/core/files/useFileUpload";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItemFileInput } from "@react-md/core/menu/MenuItemFileInput";
import { type ReactElement, useState } from "react";
const extensions = [
"svg",
"jpeg",
"jpg",
"png",
"apng",
"mkv",
"mp4",
"mpeg",
"mpg",
"webm",
"mov",
] as const;
const FOUR_HUNDRED_MB = 400 * 1024 * 1024;
const maxFiles = 10;
export default function MenuItemFileInputExample(): ReactElement {
const [file1, setFile1] = useState("");
const [file2, setFile2] = useState("");
// checkout the useFileUpload documentation for a real example with this hook
const { accept, onChange } = useFileUpload({
maxFiles,
maxFileSize: FOUR_HUNDRED_MB,
extensions,
});
return (
<DropdownMenu buttonChildren="Options">
<MenuItemFileInput
onChange={(event) => {
setFile1(event.currentTarget.value);
}}
secondaryText={`Selected file: ${file1 || "none"}`}
>
Upload
</MenuItemFileInput>
<MenuItemFileInput
onChange={(event) => {
setFile2(event.currentTarget.value);
}}
secondaryText={`Selected file: ${file2 || "none"}`}
preventMenuHideOnClick
>
Upload
</MenuItemFileInput>
<MenuItemFileInput disabled onChange={() => {}}>
Disabled
</MenuItemFileInput>
<MenuItemFileInput accept={accept} onChange={onChange} multiple>
Upload Multiple
</MenuItemFileInput>
</DropdownMenu>
);
}
MenuItemSeparator and MenuItemGroup
Use the MenuItemGroup and MenuItemSeparator when rendering the
MenuItemRadio with other groups of MenuItems.
See more at https://www.w3.org/TR/wai-aria-1.1/#menuitemradio
import { useRadioGroup } from "@react-md/core/form/useRadioGroup";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { MenuItemGroup } from "@react-md/core/menu/MenuItemGroup";
import { MenuItemRadio } from "@react-md/core/menu/MenuItemRadio";
import { MenuItemSeparator } from "@react-md/core/menu/MenuItemSeparator";
import { type ReactElement } from "react";
export default function MenuItemSeparatorAndGroupExample(): ReactElement {
const { getRadioProps } = useRadioGroup({
menu: true,
});
return (
<DropdownMenu buttonChildren="Dropdown" themeType="outline">
<MenuItemGroup aria-label="Example Radio Group">
<MenuItemRadio {...getRadioProps("a")}>Radio 1</MenuItemRadio>
<MenuItemRadio {...getRadioProps("b")}>Radio 2</MenuItemRadio>
<MenuItemRadio {...getRadioProps("c")}>Radio 3</MenuItemRadio>
</MenuItemGroup>
<MenuItemSeparator />
<MenuItem>Menu Item 1</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
<MenuItem>Menu Item 3</MenuItem>
</DropdownMenu>
);
}
Form MenuItem with Addons
The form menu item components can also render an addon like the MenuItem using
addon, addonType, addonPosition props.
In addition, if the icon checkbox, radio, or switch should be rendered after the
text instead of before, the iconAfter prop can be enabled.
import { Avatar } from "@react-md/core/avatar/Avatar";
import { useCheckboxGroup } from "@react-md/core/form/useCheckboxGroup";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItemCheckbox } from "@react-md/core/menu/MenuItemCheckbox";
import { MenuItemRadio } from "@react-md/core/menu/MenuItemRadio";
import { MenuItemSwitch } from "@react-md/core/menu/MenuItemSwitch";
import FavoriteIcon from "@react-md/material-icons/FavoriteIcon";
import { type ReactElement } from "react";
export default function FormMenuItemWithAddonsExample(): ReactElement {
const { getCheckboxProps } = useCheckboxGroup({ menu: true });
return (
<DropdownMenu buttonChildren="Dropdown">
<MenuItemCheckbox {...getCheckboxProps("a")} addon={<FavoriteIcon />}>
With Icon
</MenuItemCheckbox>
<MenuItemRadio
{...getCheckboxProps("b")}
addon={<Avatar>A</Avatar>}
addonType="avatar"
>
With Avatar
</MenuItemRadio>
<MenuItemSwitch
{...getCheckboxProps("c")}
addon={<img src="https://picsum.photos/56?image=700" alt="" />}
addonType="media"
>
With Media
</MenuItemSwitch>
<MenuItemCheckbox
{...getCheckboxProps("d")}
addon={<img src="https://picsum.photos/100/56?image=800" alt="" />}
addonType="large-media"
iconAfter
>
With Large Media
</MenuItemCheckbox>
</DropdownMenu>
);
}
Menu within a Dialog
Since menus use the Portal to render in a different part of the DOM and position within the viewport using useFixedPositioning, menus can be rendered in dialogs without causing any issues like scrollbars appearing or not being able to view all the menu items.
"use client";
import { Button } from "@react-md/core/button/Button";
import { Dialog } from "@react-md/core/dialog/Dialog";
import { DialogContent } from "@react-md/core/dialog/DialogContent";
import { DropdownMenu } from "@react-md/core/menu/DropdownMenu";
import { MenuItem } from "@react-md/core/menu/MenuItem";
import { Typography } from "@react-md/core/typography/Typography";
import { useToggle } from "@react-md/core/useToggle";
import { type ReactElement } from "react";
export default function MenuWithinADialog(): ReactElement {
const { toggled, enable, disable } = useToggle();
return (
<>
<Button onClick={enable}>Show Dialog</Button>
<Dialog aria-label="Example" visible={toggled} onRequestClose={disable}>
<DialogContent>
<Typography>
{
"Here is a paragraph of text. Even though it's really only two sentences."
}
</Typography>
<DropdownMenu buttonChildren="Dropdown">
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
<MenuItem>Item 3</MenuItem>
</DropdownMenu>
</DialogContent>
</Dialog>
</>
);
}
Accessibility
The menu components implement the menu and menu button specifications.
Keyboard Movement
The following keyboard movement has been implemented:
Menu Button
- ArrowUp - opens the menu and focuses the last menu item
- ArrowDown/Space/Enter - opens the menu and focuses the first menu item
Menu Item Button
- ArrowRight - opens the nested menu and moves focus to the first menu item in
the newly opened menu
- ArrowDown is used for horizontal menus
- ArrowLeft - closes a nested dropdown menu (if exists) and moves focus back
into the parent menu
- ArrowUp is used for horizontal menus
Menu
- ArrowUp/ArrowDown - moves the focus to the previous/next menu item and
loops
- ArrowLeft/ArrowRight will be used for horizontal menus
- Home/End - moves focus to the first and last menu item
- Enter/Space - clicks the current
MenuItem - Typing a letter will find the focus the first menu item that starts with the same letter. Pressing the same letter will continue to loop through matches.
- Typing an uppercase letter will always focus the first menu item that starts with the same letter.
- Escape - Closes the current menu
MenuBar
- While no dropdown menus are visible:
- ArrowLeft/ArrowRight will move focus to the previous/next dropdown menu items
- Home/End - moves focus to the first and last dropdown menu item
- Typing a letter will find the focus the first menu item that starts with the same letter. Pressing the same letter will continue to loop through matches.
- Typing an uppercase letter will always focus the first menu item that starts with the same letter.
- ArrowUp - opens the menu and focuses the last menu item
- ArrowDown/Space/Enter - opens the menu and focuses the first menu item
- While a dropdown menu is visible:
- ArrowLeft/ArrowRight will close the current dropdown menu, open the
previous/next dropdown menu, and move focus into the new dropdown menu on
the first item
- If a nested dropdown menu is focused, that behavior will be used instead
- ArrowLeft/ArrowRight will close the current dropdown menu, open the
previous/next dropdown menu, and move focus into the new dropdown menu on
the first item