This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

Menu

A menu compatible with @react-native-menu/menu.

Android
iOS
Included in Expo Go

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:

If you need lower-level control, use those primitives directly.

An open menu with an Edit row and a Delete rowAn open menu with an Edit row and a Delete row

Installation

Terminal
- npx expo install @expo/ui
- yarn expo install @expo/ui
- pnpm expo install @expo/ui
- bun expo install @expo/ui

If 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' to import { MenuView } from '@expo/ui/community/menu'.
  • action.image on Android differs from upstream. @react-native-menu/menu expects a drawable resource name string (for example, 'ic_menu_add') that it resolves against android/app/src/main/res/drawable/. This drop-in does not resolve drawable resource names — pass an ImageSourcePropType instead (for example, require('@expo/material-symbols/edit.xml')). String values are accepted on iOS as SF Symbol names. Use Icon.select to define both per call site so the unused side tree-shakes out per platform.
  • title is rendered as a section header on iOS only; Android's Material DropdownMenu has no title slot.
  • On Android, MenuView wraps the trigger in its own Pressable to open the menu, so an onPress/onLongPress handler attached to a Pressable you pass as children won't fire — the outer wrapper claims the gesture. Move that handler into your onPressAction switch instead, or use the lower-level DropdownMenu primitive if you need to keep separate tap and long-press actions on the trigger.
  • The imperative ref.show() API is Android-only. SwiftUI Menu/ContextMenu have 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/menu are not supported: themeVariant, hitSlop, isAnchoredToRight, subtitle, keepsMenuPresented, preferredElementSize, and state: 'mixed'.

Basic usage

Pass any view as the trigger. MenuView attaches its own tap or long-press handling.

MenuExample.tsx
import { Icon } from '@expo/ui'; import { MenuView } from '@expo/ui/community/menu'; import { Text, useColorScheme, View } 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() { const colorScheme = useColorScheme(); 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)}> <View> <Text style={{ color: colorScheme === 'dark' ? '#FFFFFF' : '#000000' }}>Open menu</Text> </View> </MenuView> ); }

Long-press (context menu)

Set shouldOpenOnLongPress to render as a context menu. On Android, the same controlled DropdownMenu opens from a long press on the trigger instead of a tap. On iOS, this uses SwiftUI's ContextMenu and shows the trigger as a blurred preview.

LongPressMenuExample.tsx
import { Icon } from '@expo/ui'; import { MenuView } from '@expo/ui/community/menu'; import { Text, useColorScheme, View } 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() { const colorScheme = useColorScheme(); return ( <MenuView shouldOpenOnLongPress actions={[ { id: 'copy', title: 'Copy', image: copyIcon }, { id: 'share', title: 'Share', image: shareIcon }, ]} onPressAction={e => console.log(e.nativeEvent.event)}> <View style={{ padding: 8 }}> <Text style={{ color: colorScheme === 'dark' ? '#FFFFFF' : '#000000' }}>Long-press me</Text> </View> </MenuView> ); }

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.

SubmenuExample.tsx
import { MenuView } from '@expo/ui/community/menu'; import { Text, useColorScheme, View } from 'react-native'; export default function SubmenuExample() { const colorScheme = useColorScheme(); 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)}> <View> <Text style={{ color: colorScheme === 'dark' ? '#FFFFFF' : '#000000' }}>Open menu</Text> </View> </MenuView> ); }

Toggle items with checkmarks

Set state to 'on' or 'off' to render an action as a toggleable item with a leading checkmark when on. Selecting the action fires onPressAction and the caller is responsible for updating the state.

ToggleMenuExample.tsx
import { MenuView } from '@expo/ui/community/menu'; import { useState } from 'react'; import { Text, useColorScheme, View } from 'react-native'; export default function ToggleMenuExample() { const colorScheme = useColorScheme(); 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); }}> <View> <Text style={{ color: colorScheme === 'dark' ? '#FFFFFF' : '#000000' }}> {pinned ? 'Pinned' : 'Not pinned'} </Text> </View> </MenuView> ); }

API

import { MenuView } from '@expo/ui/community/menu';

No API data file found, sorry!