A menu displays a list of choices on a temporary surface. They appear when users interact with a button, action, or other control.
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>
);
}
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>
);
}
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>
);
}
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>
</>
);
}
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>
);
}
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>
</>
);
}
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>
);
}
The Menu uses the useFixedPositioning hook to position itself
within the viewport. Check out the
useFixedPositioning documentation page for
available options.
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>
</>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
</>
);
}
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 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>
);
}
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>
</>
);
}
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>
</>
);
}
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>
);
}
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>
);
}
The following components have been provided to merge the functionality and
accessibility into a MenuItem.
MenuItemCheckboxMenuItemRadioMenuItemSwitchMenuItemTextFieldMenuItemFileInputMenuItemSeparatorMenuItemGroupThese 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.
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
</>
);
}
The menu components implement the menu and menu button specifications.
The following keyboard movement has been implemented:
MenuItem