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 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.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 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.Root>
  );
}
PropTypeDefaultDescription
style((false | "" | ViewStyle | RegisteredStyle<ViewStyle> | RecursiveArray<Falsy | ViewStyle | RegisteredStyle<ViewStyle>> | null) & ViewStyle) | undefined
closeOnSelectboolean | undefined
onCloseRequired() => void
childrenRequiredReactElement<unknown, string | JSXElementConstructor<any>> | ReactElement<unknown, string | JSXElementConstructor<any>>[]
backdropStylesViewStyle | undefined
itemsDefaultListItemT[] | undefined
valuestring | number | (string | number)[] | null | undefined
defaultValuestring | number | (string | number)[] | null | undefined
onChange((value: DefaultListItemT["id"] | DefaultListItemT["id"][] | null, item: DefaultListItemT) => void) | undefined
multipleboolean | undefined
disabledboolean | undefined
labelKeystring | undefined
searchKeystring | undefined
onSearchChange((query: string) => void) | undefined
hideSelectedboolean | undefined
Defined in react-native-molecules/components/Menu