Skip to main content

Menu

Overlay & Menus

Menu

Context menu built on Popover + Dropdown primitives with keyboard support.

Usage

Attach contextual actions to buttons, chips, or icon triggers.

Highlights

  • MenuItem subcomponent
  • Close-on-select and focus restore behaviors

When to use it

  • User needs a small list of contextual actions.
  • You require cascading operations without navigation.

Compound Components

ComponentDescription
Menu.Item-
List.Content-
List.Item-

Examples

Default

Preview (Web)
import { StyleSheet } from 'react-native';
import { Button } from 'react-native-molecules/components/Button';
import { Icon } from 'react-native-molecules/components/Icon';
import { Divider } from 'react-native-molecules/components/Divider';
import { Text } from 'react-native-molecules/components/Text';

const styles = StyleSheet.create({
  menu: {
      width: 280,
  },
});

export default function Example() {
  return (
      <Menu.Root>
          <Menu.Trigger>
              <Button>Toggle</Button>
          </Menu.Trigger>
          <Menu.Popover>
              <Menu style={styles.menu}>
                  <Menu.Item
                      left={<Icon name="content-cut" size={22} />}
                      right={<Text>⌘X</Text>}
                      onPress={() => console.log('cut')}>
                      <Text typescale="labelLarge">Cut</Text>
                  </Menu.Item>
                  <Menu.Item
                      left={<Icon name="content-copy" size={22} />}
                      right={<Text>⌘C</Text>}
                      onPress={() => console.log('copy')}>
                      <Text typescale="labelLarge">Copy</Text>
                  </Menu.Item>
                  <Menu.Item
                      left={<Icon name="content-paste" size={22} />}
                      right={<Text>⌘V</Text>}
                      onPress={() => console.log('paste')}>
                      <Text typescale="labelLarge">Paste</Text>
                  </Menu.Item>
                  <Divider />
                  <Menu.Item left={<Icon name="cloud" size={22} />} onPress={() => console.log('web-clipboard')}>
                      <Text typescale="labelLarge">Web Clipboard</Text>
                  </Menu.Item>
              </Menu>
          </Menu.Popover>
      </Menu.Root>
  );
}

Context Menu

Open the menu at the cursor position on right-click. The menu's triggerRef accepts any object with a measureInWindow method, so you can swap in a "virtual" ref pointing at the mouse coordinates. Also provide a contains method that returns false so any outside click closes the menu.

Preview (Web)
import { useContext, useEffect, useRef } from 'react';
import { StyleSheet, View } from 'react-native';
import { Surface } from 'react-native-molecules/components/Surface';
import { Text } from 'react-native-molecules/components/Text';
import { Icon } from 'react-native-molecules/components/Icon';

const styles = StyleSheet.create({
  target: {
      padding: 48,
      alignItems: 'center',
      justifyContent: 'center',
      borderRadius: 8,
  },
  menu: {
      width: 220,
  },
});

function ContextMenuTrigger({ children }) {
  const { onOpen, triggerRef: menuTriggerRef } = useContext(Menu.RootContext);
  const targetRef = useRef(null);

  useEffect(() => {
      const el = targetRef.current;
      if (!el) return;
      const handler = (e) => {
          e.preventDefault();
          const { clientX, clientY } = e;
          menuTriggerRef.current = {
              measureInWindow: (cb) => cb(clientX, clientY, 1, 1),
              contains: () => false,
          };
          onOpen();
      };
      el.addEventListener('contextmenu', handler);
      return () => el.removeEventListener('contextmenu', handler);
  }, [onOpen, menuTriggerRef]);

  return <View ref={targetRef}>{children}</View>;
}

export default function Example() {
  return (
      <Menu.Root>
          <ContextMenuTrigger>
              <Surface elevation={1} style={styles.target}>
                  <Text>Right-click anywhere here</Text>
              </Surface>
          </ContextMenuTrigger>
          <Menu.Popover>
              <Menu style={styles.menu} position="bottom" align="start" offset={0}>
                  <Menu.Item
                      left={<Icon name="content-cut" size={20} />}
                      onPress={() => console.log('cut')}>
                      <Text typescale="labelLarge">Cut</Text>
                  </Menu.Item>
                  <Menu.Item
                      left={<Icon name="content-copy" size={20} />}
                      onPress={() => console.log('copy')}>
                      <Text typescale="labelLarge">Copy</Text>
                  </Menu.Item>
                  <Menu.Item
                      left={<Icon name="content-paste" size={20} />}
                      onPress={() => console.log('paste')}>
                      <Text typescale="labelLarge">Paste</Text>
                  </Menu.Item>
              </Menu>
          </Menu.Popover>
      </Menu.Root>
  );
}
PropTypeDefaultDescription
childrenRequiredReactElement<unknown, string | JSXElementConstructor<any>> | ReactElement<unknown, string | JSXElementConstructor<any>>[]
closeOnSelectboolean | undefined
disabledboolean | undefined
errorboolean | undefined
allowDeselectboolean | undefined
multipleboolean | undefined
valueListItemId | ListItemId[] | null | undefined
defaultValueListItemId | ListItemId[] | null | undefined
onChange((value: ListValue<false>, item: DefaultListItemT, event?: GestureResponderEvent) => void) | ((value: ListValue<true>, item: DefaultListItemT, event?: GestureResponderEvent) => void) | undefined
Defined in react-native-molecules/components/Menu