Menu
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
| Component | Description |
|---|---|
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> ); }
| Prop | Type | Default | Description |
|---|---|---|---|
style | ((false | "" | ViewStyle | RegisteredStyle<ViewStyle> | RecursiveArray<Falsy | ViewStyle | RegisteredStyle<ViewStyle>> | null) & ViewStyle) | undefined | — | — |
closeOnSelect | boolean | undefined | — | — |
onCloseRequired | () => void | — | — |
childrenRequired | ReactElement<unknown, string | JSXElementConstructor<any>> | ReactElement<unknown, string | JSXElementConstructor<any>>[] | — | — |
backdropStyles | ViewStyle | undefined | — | — |
items | DefaultListItemT[] | undefined | — | — |
value | string | number | (string | number)[] | null | undefined | — | — |
defaultValue | string | number | (string | number)[] | null | undefined | — | — |
onChange | ((value: DefaultListItemT["id"] | DefaultListItemT["id"][] | null, item: DefaultListItemT) => void) | undefined | — | — |
multiple | boolean | undefined | — | — |
disabled | boolean | undefined | — | — |
labelKey | string | undefined | — | — |
searchKey | string | undefined | — | — |
onSearchChange | ((query: string) => void) | undefined | — | — |
hideSelected | boolean | undefined | — | — |