Menu
A menu compatible with @react-native-menu/menu.
For the complete documentation index, see llms.txt. Use this file to discover all available pages.
A MenuView component with an API compatible with @react-native-menu/menu. Supports both single-tap (default) and long-press (shouldOpenOnLongPress) triggers.
Under the hood this component wraps the platform-specific @expo/ui primitives:
- Android: Jetpack Compose DropdownMenu anchored to a
Pressabletrigger. - iOS: SwiftUI Menu for tap triggers and SwiftUI ContextMenu for long-press triggers.
If you need lower-level control, use those primitives directly.
Installation
- npx expo install @expo/uiIf you are installing this in an existing React Native app, make sure to install expo in your project.
Migrating from @react-native-menu/menu
- Update the import from
import { MenuView } from '@react-native-menu/menu'toimport { MenuView } from '@expo/ui/community/menu'. action.imageon Android differs from upstream.@react-native-menu/menuexpects a drawable resource name string (for example,'ic_menu_add') that it resolves againstandroid/app/src/main/res/drawable/. This drop-in does not resolve drawable resource names — pass anImageSourcePropTypeinstead (for example,require('@expo/material-symbols/edit.xml')). String values are accepted on iOS as SF Symbol names. UseIcon.selectto define both per call site so the unused side tree-shakes out per platform.titleis rendered as a section header on iOS only; Android's MaterialDropdownMenuhas no title slot.- On Android,
MenuViewwraps the trigger in its ownPressableto open the menu, so anonPress/onLongPresshandler attached to aPressableyou pass aschildrenwon't fire — the outer wrapper claims the gesture. Move that handler into youronPressActionswitch instead, or use the lower-levelDropdownMenuprimitive if you need to keep separate tap and long-press actions on the trigger. - The imperative
ref.show()API is Android-only. SwiftUIMenu/ContextMenuhave no programmatic open API, so on iOS the call is a no-op (with a one-time dev warning). - The following props from
@react-native-menu/menuare not supported:themeVariant,hitSlop,isAnchoredToRight,subtitle,keepsMenuPresented,preferredElementSize, andstate: 'mixed'.
Basic usage
import { Icon } from '@expo/ui'; import { MenuView } from '@expo/ui/community/menu'; import { Pressable, Text } from 'react-native'; const editIcon = Icon.select({ ios: 'pencil', android: import('@expo/material-symbols/edit.xml'), }); const deleteIcon = Icon.select({ ios: 'trash', android: import('@expo/material-symbols/delete.xml'), }); export default function MenuExample() { return ( <MenuView actions={[ { id: 'edit', title: 'Edit', image: editIcon }, { id: 'delete', title: 'Delete', image: deleteIcon, attributes: { destructive: true } }, ]} onPressAction={e => console.log(e.nativeEvent.event)}> <Pressable> <Text>Open menu</Text> </Pressable> </MenuView> ); }
Long-press (context menu)
Set shouldOpenOnLongPress to render as a context menu. On Android, the same controlled DropdownMenu opens from the Pressable's onLongPress instead of onPress. On iOS, this uses SwiftUI's ContextMenu and shows the trigger as a blurred preview.
import { Icon } from '@expo/ui'; import { MenuView } from '@expo/ui/community/menu'; import { Pressable, Text } from 'react-native'; const copyIcon = Icon.select({ ios: 'doc.on.doc', android: import('@expo/material-symbols/content_copy.xml'), }); const shareIcon = Icon.select({ ios: 'square.and.arrow.up', android: import('@expo/material-symbols/share.xml'), }); export default function LongPressMenuExample() { return ( <MenuView shouldOpenOnLongPress actions={[ { id: 'copy', title: 'Copy', image: copyIcon }, { id: 'share', title: 'Share', image: shareIcon }, ]} onPressAction={e => console.log(e.nativeEvent.event)}> <Pressable> <Text>Long-press me</Text> </Pressable> </MenuView> ); }
Submenus and inline sections
subactions renders nested actions as a submenu by default. Set displayInline: true on the parent to render the children as an inline section instead, which is useful for grouping. On Android, only the divider appears (Material's DropdownMenu has no section primitive). On iOS, the parent's title becomes the section header.
import { MenuView } from '@expo/ui/community/menu'; import { Pressable, Text } from 'react-native'; export default function SubmenuExample() { return ( <MenuView actions={[ { id: 'rename', title: 'Rename' }, { id: 'sort', title: 'Sort by', subactions: [ { id: 'sort-name', title: 'Name' }, { id: 'sort-date', title: 'Date' }, { id: 'sort-size', title: 'Size' }, ], }, { id: 'share-section', title: 'Share', displayInline: true, subactions: [ { id: 'share-airdrop', title: 'AirDrop' }, { id: 'share-message', title: 'Message' }, ], }, ]} onPressAction={e => console.log(e.nativeEvent.event)}> <Pressable> <Text>Open menu</Text> </Pressable> </MenuView> ); }
Toggle items with checkmarks
Set state to 'on' or 'off' to render an action as a togglable item with a leading checkmark when on. Selecting the action fires onPressAction and the caller is responsible for updating the state.
import { MenuView } from '@expo/ui/community/menu'; import { useState } from 'react'; import { Pressable, Text } from 'react-native'; export default function ToggleMenuExample() { const [pinned, setPinned] = useState(false); return ( <MenuView actions={[{ id: 'pin', title: 'Pin to top', state: pinned ? 'on' : 'off' }]} onPressAction={e => { if (e.nativeEvent.event === 'pin') setPinned(p => !p); }}> <Pressable> <Text>{pinned ? 'Pinned' : 'Not pinned'}</Text> </Pressable> </MenuView> ); }
API
import { MenuView } from '@expo/ui/community/menu';
Component
Type: React.Element<MenuComponentProps & {
ref: Ref<MenuComponentRef>
}>
A drop-in replacement for @react-native-menu/menu's MenuView. Wrap any trigger
view; long-pressing or tapping (per shouldOpenOnLongPress) shows a popup menu
built from the actions tree.
- On Android, renders via Compose's
DropdownMenuanchored to aPressable. - On iOS, renders via SwiftUI's
Menu(tap) orContextMenu(long-press). - On web, the trigger renders the trigger but actions do not fire;
a one-time
console.warnis emitted.
Props
ReactNodeTrigger view. Long-pressing or tapping (per shouldOpenOnLongPress) opens the menu.
() => voidCallback invoked when the menu closes (either via dismissal or after an action fires).
On Android, fires from the controlled DropdownMenu's dismiss path.
On iOS, SwiftUI Menu/ContextMenu do not expose a close hook in a way we can
forward, so this is not fired there.
() => voidCallback invoked when the menu opens.
On Android, fires when the trigger's tap/long-press flips expanded to true.
On iOS, SwiftUI Menu/ContextMenu do not expose an open hook, so this is not
fired there.
(event: NativeActionEvent) => voidCallback invoked when a menu action is selected.
boolean • Default: falseWhen true, the menu opens on long-press of the trigger instead of a single tap.
Types
A single action inside a MenuView.
Compatible with @react-native-menu/menu.
| Property | Type | Description |
|---|---|---|
| attributes(optional) | MenuAttributes | Visual/behavioral flags. |
| displayInline(optional) | boolean | When |
| id(optional) | string | Identifier passed back via |
| image(optional) | SFSymbol | ImageSourcePropType | Icon to render beside the action label.
|
| imageColor(optional) | ColorValue | Tint color applied to the action's icon. Visually applied on Android via the leading |
| state(optional) | MenuState | Selection state. When |
| subactions(optional) | MenuAction[] | Nested actions. Without |
| title | string | Action label shown in the menu. |
| titleColor(optional) | ColorValue | Only for: Android Text color of the action label. |
Visual and behavioral attributes of a menu action.
Compatible with @react-native-menu/menu.
| Property | Type | Description |
|---|---|---|
| destructive(optional) | boolean | Renders the action with a destructive style (red text/icon). |
| disabled(optional) | boolean | Disables the action so it can't be activated. |
| hidden(optional) | boolean | Hides the action from the menu. |
Imperative handle exposed by MenuView via ref.
Compatible with @react-native-menu/menu's ref.show() API.
| Property | Type | Description |
|---|---|---|
| show | () => void | Only for: Android Programmatically open the menu. On Android, opens the anchored |
Literal Type: string
Selection state for a menu action.
'on' renders a checkmark; 'off' doesn't.
Acceptable values are: 'on' | 'off'