JGuang

Menu

Menu · Mantine Core

Preview
npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1

Menu

Install: npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1

Version and compatibility

Pinned Mantine Core 9.7.1 requires React and React DOM ^19.2.0 and matching @mantine/hooks@9.7.1, from the installed npm package's peerDependencies. Do not force it into React 18 or another incompatible version. Explain upgrade impact and agree on a plan with the project owner; do not hide conflicts with --force/--legacy-peer-deps. The native Provider enables theme.respectReducedMotion and uses forceColorScheme for light/dark.

Use case

Expose a menu of actions from a trigger.

Native props used by this example

These props are extracted from the local typechecked JSX and grouped by their owning native component. This is not an exhaustive prop list; see pinned source signatures and inheritance below.

Component propExplanation
Menu.Item.colorNative color value or theme color key.

Interaction and state checks

Current scenarios: Default. Check actual behavior after selecting supported disabled/error/read-only scenarios; components without those native states do not receive invented universal props. Verify keyboard focus and exits, then connect changed values through the native callback signature.

Business integration boundary

Demo content/state is local, without server persistence, authorization or fetching. A static display component is not a completed business workflow.

Host setup

Install pinned @mantine/core and @mantine/hooks, import @mantine/core/styles.css at the entry and use MantineProvider. The example's theme/variant are JGuang-authored host props, not universal native component APIs; forceColorScheme synchronizes the app theme. Native implementation source is a reading reference: use npm exports rather than copying internal engines. This batch covers Core, not dates/charts/forms extension packages. Detailed upstream prose remains English.

Runnable usage

import { useState } from 'react';
import * as M from '@mantine/core';
import '@mantine/core/styles.css';


function ComponentPreview({ variant = 'Default' }: { variant?: string }) {

return <M.Menu><M.Menu.Target><M.Button>Open menu</M.Button></M.Menu.Target><M.Menu.Dropdown><M.Menu.Item>Settings</M.Menu.Item><M.Menu.Item>Profile</M.Menu.Item><M.Menu.Divider /><M.Menu.Item color="red">Delete</M.Menu.Item></M.Menu.Dropdown></M.Menu>;
}
export default function Example({ variant = 'Default', theme = 'light' }: { variant?: string; theme?: 'light' | 'dark' }) {
return <M.MantineProvider forceColorScheme={theme} theme={{ respectReducedMotion: true }}><ComponentPreview variant={variant} /></M.MantineProvider>;
}

Preview states

Default

The example uses the native Mantine API. For supported input demos, change the local variant to Disabled, Invalid or Read only; these are example labels, not new component props. Keep the documented data, controlled values, stores and compound children when adapting the example.

Usage

Use safeAreaPolygon to keep a submenu open while the cursor moves from the target toward the dropdown. Pass an object to adjust Floating UI safePolygon options, for example when offset creates a larger gap between the target and dropdown.

Menu.Search renders a search input inside the dropdown. Focus stays on the input while ArrowUp/ArrowDown move a highlight through items, and Enter triggers the highlighted item. Filtering is controlled by the user – pass value and onChange and filter Menu.Item children based on the query. By default, the search value is cleared automatically after the menu close transition completes – disable with clearSearchOnClose={false} if you want to preserve the query between openings.

The same approach works with submenus. To keep a parent visible when a nested item matches, include the parent in the filtered tree if any of its descendants match the query:

Checkbox items

Menu.CheckboxItem renders a menu item with a check indicator. It works like a regular Checkbox – manage state with checked/onChange or use defaultChecked for an uncontrolled value. By default, clicking a checkbox item does not close the menu; set closeMenuOnClick on the item (or closeOnItemClick={false} on the Menu) to override:

Checkbox group

Wrap Menu.CheckboxItem components in Menu.CheckboxGroup to manage multi-select state with a single value: string[] / onChange pair (or defaultValue for uncontrolled). Each item needs a value. Clicking an item toggles its value in the group.

Menu.CheckboxItem can still be used standalone (without a group) with its own checked / defaultChecked / onChange props. Item-level checked and onChange also override the group when both are present.

Radio items

Menu.RadioItem represents a single option inside a Menu.RadioGroup. The group manages the selected value via value/onChange (or defaultValue for uncontrolled). The currently selected item displays an indicator dot. Like checkbox items, radio items do not close the menu on click by default:

Aligning labels of items with and without indicators

Use the alignItemsLabels prop on Menu to control how indicator slot space is reserved. This is useful when mixing Menu.Item with Menu.CheckboxItem or Menu.RadioItem and you want labels to start at the same horizontal position:

  • alignItemsLabels="with-indicators" (default) – reserves indicator space on Menu.CheckboxItem and Menu.RadioItem only. Regular Menu.Item is not padded.
  • alignItemsLabels="all" – reserves indicator space on every Menu.Item, so labels of plain items align with checkbox and radio items.
  • alignItemsLabels="none" – reserves indicator space only on items that currently show an indicator. Unchecked checkbox and radio items render without the slot (layout shifts when toggled).

Custom check icon

Use the checkIcon prop to replace the default indicator rendered by Menu.CheckboxItem and Menu.RadioItem. Setting checkIcon on Menu applies to all checkbox/radio items in the dropdown. Setting checkIcon on an individual item overrides the menu-level value:

Type-ahead navigation

When focus is inside the dropdown and Menu.Search is not used, pressing a printable character key moves focus to the next menu item whose label starts with the typed character. Pressing the same character again cycles through items that start with it. Multiple characters typed in quick succession (within 500ms) match items whose labels start with the full typed string. Disabled items are skipped.

Context menu

Use Menu.ContextMenu to open the menu dropdown at the cursor position on right-click. It replaces Menu.Target and wraps the element that should respond to the contextmenu event – the browser's default context menu is suppressed, and the Mantine Menu.Dropdown is positioned at the cursor instead. Right-clicking again repositions the dropdown to the new coordinates. Set disabled to restore the browser's default context menu:

Touch devices

On touch devices (most notably iOS Safari, which does not fire the contextmenu event), the dropdown is opened with a long-press instead. Use the longPressDelay prop to control how long the element must be pressed before the dropdown opens, 500ms by default:

import { Menu } from '@mantine/core';

function Demo() {
  return (
    <Menu>
      <Menu.ContextMenu longPressDelay={400}>
        <div>Long-press me</div>
      </Menu.ContextMenu>
      <Menu.Dropdown>
        <Menu.Item>Copy</Menu.Item>
        <Menu.Item>Paste</Menu.Item>
      </Menu.Dropdown>
    </Menu>
  );
}

To prevent the native text-selection callout from appearing under the dropdown on touch devices, Menu.ContextMenu disables text selection (user-select: none) on the wrapped element.

Controlled

The dropdown's opened state can be controlled with the opened and onChange props:

import { useState } from 'react';
import { Menu } from '@mantine/core';

function Demo() {
  const [opened, setOpened] = useState(false);
  return (
    <Menu opened={opened} onChange={setOpened}>
      {/* Menu content */}
    </Menu>
  );
}

Show menu on hover

Set trigger="hover" to reveal the dropdown when hovering over the menu target and dropdown. The closeDelay and openDelay props can be used to control open and close delay in ms. Note that:

  • If you set closeDelay={0} then the menu will close before the user reaches the dropdown, so set offset={0} to remove space between the target element and dropdown.
  • Menu with trigger="hover" is not accessible – users that navigate with the keyboard will not be able to use it. If you need both hover and click triggers, use trigger="click-hover".

To make a Menu that is revealed on hover accessible on all devices, use trigger="click-hover" instead. The dropdown will be revealed on hover on desktop and on click on mobile devices.

Disabled items

Transitions

The Menu dropdown can be animated with any of the premade transitions from the Transition component:

Custom component as Menu.Item

By default, Menu.Item renders as a button element. To change that, set the component prop:

Note that the component you pass to the component prop should allow spreading props to its root element:

import { Menu } from '@mantine/core';

// ❌ Will not work with Menu.Item
function IncorrectItem() {
  return <button type="button">My custom Menu item</button>;
}

// ✅ Will work correctly with Menu.Item
const CorrectItem = ({ ref, ...props }) => (
  <button type="button" {...props} ref={ref}>
    My custom Menu item
  </button>
);

function Demo() {
  // ❌ Will not work
  const incorrect = <Menu.Item component={IncorrectItem} />;

  // ✅ Will work
  const correct = <Menu.Item component={CorrectItem} />;
}

Custom component as target

Accessibility

Menu follows WAI-ARIA recommendations:

  • Dropdown element has role="menu" and aria-labelledby="target-id" attributes
  • Target element has aria-haspopup="menu", aria-expanded, aria-controls="dropdown-id" attributes
  • Menu item has role="menuitem" attribute

Whilst the dropdown is unopened, the aria-controls attribute will be undefined

Supported target elements

An uncontrolled Menu with trigger="click" (default) will be accessible only when used with a button element or component that renders it (Button, ActionIcon, etc.). Other elements will not support Space and Enter key presses.

Hover menu

Menu with trigger="hover" is not accessible – it cannot be accessed with the keyboard. Use it only if you do not care about accessibility. If you need both hover and click triggers, use trigger="click-hover".

If you are using the Menu to build navigation, you can use the options from the demo below to follow the WAI-ARIA recommendations for navigation.

Keyboard interactions

If you also need to support Tab and Shift + Tab then set menuItemTabIndex={0}.

Source-backed parameter reference

Implementation signatures and types are generated from the pinned local source. Optional props, native inherited types, union branches and never exclusions are preserved. A forwardRef implementation's ref is a React reference, not an additional business prop.

Menu(_props)
export type MenuStylesNames =
  | 'item'
  | 'itemLabel'
  | 'itemSection'
  | 'itemIndicator'
  | 'label'
  | 'divider'
  | 'chevron'
  | 'search'
  | PopoverStylesNames;

export type MenuFactory = Factory<{
  props: MenuProps;
  stylesNames: MenuStylesNames;
  staticComponents: {
    Item: typeof MenuItem;
    Label: typeof MenuLabel;
    Dropdown: typeof MenuDropdown;
    Target: typeof MenuTarget;
    Divider: typeof MenuDivider;
    Search: typeof MenuSearch;
    Sub: typeof MenuSub;
    CheckboxItem: typeof MenuCheckboxItem;
    CheckboxGroup: typeof MenuCheckboxGroup;
    RadioItem: typeof MenuRadioItem;
    RadioGroup: typeof MenuRadioGroup;
    ContextMenu: typeof MenuContextMenu;
  };
}>;

export interface MenuProps extends __PopoverProps, StylesApiProps<MenuFactory> {
  variant?: string;

  /** Menu children */
  children?: React.ReactNode;

  /** Controlled menu opened state */
  opened?: boolean;

  /** Uncontrolled menu initial opened state */
  defaultOpened?: boolean;

  /** If set, focus is trapped within the menu dropdown when it is opened */
  trapFocus?: boolean;

  /** Called when menu opened state changes */
  onChange?: (opened: boolean) => void;

  /** Called when Menu is opened */
  onOpen?: () => void;

  /** Called when Menu is closed */
  onClose?: () => void;

  /** If set, the Menu is closed when one of the items is clicked. Can be overridden per item with `closeMenuOnClick` prop */
  closeOnItemClick?: boolean;

  /** If set, arrow key presses wrap around from last item to first and vice versa */
  loop?: boolean;

  /** If set, the dropdown is closed when the `Escape` key is pressed @default true */
  closeOnEscape?: boolean;

  /** Event trigger to open menu. Note: 'hover' is not keyboard accessible; prefer 'click-hover' for accessible hover menus */
  trigger?: 'click' | 'hover' | 'click-hover';

  /** Open delay in ms, applicable only to `trigger="hover"` variant */
  openDelay?: number;

  /** Close delay in ms, applicable only to `trigger="hover"` variant */
  closeDelay?: number;

  /** If set, the dropdown is closed on outside clicks */
  closeOnClickOutside?: boolean;

  /** Events that trigger outside click detection. Includes mousedown for desktop clicks, touchstart for mobile, and keydown for Escape key handling @default ['mousedown', 'touchstart', 'keydown'] */
  clickOutsideEvents?: string[];

  /** Id base to create accessibility connections */
  id?: string;

  /** Set the `tabindex` on all menu items. Use `0` to allow Tab key navigation through menu items (required for navigation menus following WAI-ARIA disclosure pattern). @default -1 */
  menuItemTabIndex?: -1 | 0;

  /** Adds a hidden focusable element at the start of the dropdown to prevent unexpected focus jumps when opening with keyboard. Set to false if you need custom focus management. @default true */
  withInitialFocusPlaceholder?: boolean;

  /** Determines whether focus should be automatically returned to control when dropdown closes @default `true` */
  returnFocus?: boolean;

  /** Controls how indicator slot space is reserved on menu items for label alignment. `'all'` reserves space on every `Menu.Item`, `'with-indicators'` reserves space only on `Menu.CheckboxItem` and `Menu.RadioItem`, `'none'` reserves space only on items that currently display an indicator. @default 'with-indicators' */
  alignItemsLabels?: 'all' | 'with-indicators' | 'none';

  /** Custom icon rendered as the indicator of checked `Menu.CheckboxItem` and selected `Menu.RadioItem`. Can be overridden per item with the `checkIcon` prop. */
  checkIcon?: React.ReactNode;
}

Native and external types

External types remain defined by React or native dependencies, not manually expanded into guessed props. The installed version's TypeScript definitions are authoritative.

Factory<{
  props: MenuProps;
  stylesNames: MenuStylesNames;
  staticComponents: {
    Item: typeof MenuItem;
    Label: typeof MenuLabel;
    Dropdown: typeof MenuDropdown;
    Target: typeof MenuTarget;
    Divider: typeof MenuDivider;
    Search: typeof MenuSearch;
    Sub: typeof MenuSub;
    CheckboxItem: typeof MenuCheckboxItem;
    CheckboxGroup: typeof MenuCheckboxGroup;
    RadioItem: typeof MenuRadioItem;
    RadioGroup: typeof MenuRadioGroup;
    ContextMenu: typeof MenuContextMenu;
  };
}>
__PopoverProps
StylesApiProps<MenuFactory>
React.ReactNode
PopoverStylesNames