Native iOS context menus (long-press / 3D Touch) for React Native — built on the New Architecture (Fabric) with a composable, slot-based API.
I’m building this package in my free time (evenings and weekends), so development moves at a ~weekly cadence. If you need a feature, found a bug, or have any request — please open an issue on GitHub. That helps me prioritise what matters most to the community.
This package is compiled with the React Compiler. The published code is automatically optimized and memoized.
- React Native 0.76+ (Fabric / New Architecture)
- React 19.0+
- iOS 13.0+
- Xcode 15.0+
The old architecture (Paper) is not supported. Make sure newArchEnabled=true in your Podfile / gradle.properties.
yarn add @simonegauli/react-native-context-menuThen install the CocoaPods:
cd ios && pod installNote: Android and Web currently throw a runtime error ("not supported on this platform"). Platform-level stubs will ship in a future release.
⚠️ Important: React Native's built-inPressablehas a known press/long-press race condition —onPressmay fire even when the user intends a long-press to open the context menu. Usereact-native-gesture-handler'sPressableinstead, which correctly cancels the tap when a long-press is detected. Wrap your app (or at least the screen) inGestureHandlerRootView.
yarn add react-native-gesture-handlerimport { StrictMode } from 'react';
import { Alert, StyleSheet, Text, View } from 'react-native';
import {
GestureHandlerRootView,
Pressable,
} from 'react-native-gesture-handler';
import * as ContextMenu from '@simonegauli/react-native-context-menu';
export default function App() {
return (
<StrictMode>
<GestureHandlerRootView style={{ flex: 1 }}>
<View style={styles.container}>
<ContextMenu.Root>
<ContextMenu.Trigger>
<Pressable
style={styles.box}
onPress={() => Alert.alert('Button pressed')}
>
<Text style={styles.label}>Tap or hold</Text>
</Pressable>
</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item
id="share"
onPress={() => Alert.alert('Share pressed')}
>
<ContextMenu.ItemTitle>Share</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon ios="square.and.arrow.up" />
</ContextMenu.Item>
<ContextMenu.Item
id="copy"
onPress={() => Alert.alert('Copy pressed')}
>
<ContextMenu.ItemTitle>Copy</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon ios="doc.on.doc" />
</ContextMenu.Item>
<ContextMenu.Item
id="delete"
destructive
onPress={() => Alert.alert('Delete pressed')}
>
<ContextMenu.ItemTitle>Delete</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon ios="trash" />
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>
</View>
</GestureHandlerRootView>
</StrictMode>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#1a1a2e',
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 160,
height: 80,
borderRadius: 16,
backgroundColor: 'rgba(255,255,255,0.15)',
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.4)',
alignItems: 'center',
justifyContent: 'center',
},
boxPressed: { backgroundColor: 'rgba(255,255,255,0.3)' },
label: { color: '#fff', fontWeight: '600', fontSize: 16 },
});Wraps the context-menu interaction. Must contain one Trigger and one Content.
| Prop | Type | Required | Description |
|---|---|---|---|
children |
ReactNode |
Yes | Must include a Trigger and a Content |
style |
StyleProp<ViewStyle> |
No | Styles applied to the native wrapper view |
Declares the view that receives the long-press / context-menu gesture. Accepts any ReactNode as its single child (typically a Pressable, View, or Image).
On iOS the gesture is handled by
UIContextMenuInteractionattached to the native view. Standard tap behaviour (e.g.,onPress) is fully preserved.
| Prop | Type | Required | Description |
|---|---|---|---|
children |
ReactNode |
Yes | The element that triggers the context menu |
Declares the menu items. Only renders Item components — other children are accepted but ignored.
| Prop | Type | Required | Description |
|---|---|---|---|
children |
ReactNode |
Yes | One or more ContextMenu.Item elements |
A single menu action. Must contain an ItemTitle child.
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string |
— (required) | Unique identifier for this item |
children |
ReactNode |
— (required) | Must include an ItemTitle |
destructive |
boolean |
false |
Renders the item with destructive (red) styling |
disabled |
boolean |
false |
Greys-out the item and makes it non-interactive |
onPress |
() => void |
undefined |
Callback invoked when the user taps the item |
Sets the label for the parent Item. Must be a direct child of Item. Children must be a plain string.
| Prop | Type | Required | Description |
|---|---|---|---|
children |
string |
Yes | Label displayed in the menu |
Attaches an icon to the parent Item. Must be a direct child of Item. Uses typed SF Symbol names from sf-symbols-typescript for full autocomplete.
| Prop | Type | Platform | Description |
|---|---|---|---|
ios |
SFSymbol |
iOS | SF Symbol name (e.g. 'trash', 'square.and.arrow.up'). Autocompleted from the full catalogue |
androidIconName |
string |
Android | Android resource drawable name |
What’s coming. Ordered roughly by priority — open an issue if you’d like to see something bumped up.
- Item subtitle —
subtitleprop to show a secondary line under each item - Item customisation parity — bring feature parity with similar packages such as zeego (inline previews, sub-menus, toggle / stateful items, custom preview providers)
- Android support — replace the current throw with a real Android
PopupMenu/FloatingToolbarimplementation - Web support — replace the current throw with a working HTML/CSS context menu (native
onContextMenuor custom overlay) - Platform stubs — render
childrengracefully on unsupported platforms instead of throwing - Expo config plugin — auto-link for managed Expo + CNG setups
- Menu lifecycle callbacks —
onMenuWillShow,onMenuWillHideevents - Dynamic items — update menu contents at runtime without unmounting
Contributions are welcome! See CONTRIBUTING.md for the development workflow and pull request guidelines.
MIT — see LICENSE.