A native menu component for React Native that provides platform-specific context menus for both Android and iOS. Pass any custom component as a child to trigger native menus.
![]() iOS (Native UIMenu) |
![]() Android (Modal Dialog) |
- âś… Native context menu implementation (UIMenu on iOS, Modal on Android)
- âś… Custom trigger components - pass any React Native component as a child
- âś… Customizable colors for menu items
- âś… Checkmark support with custom colors
- âś… Scrollable menus for long lists
- âś… Event handling for menu item selection
- âś… TypeScript support
- âś… Fabric (New Architecture) compatible
npm install react-native-menus
# or
yarn add react-native-menus
For iOS, run:
cd ios && pod install
No additional setup required for Android.
import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { MenuView } from 'react-native-menus';
const App = () => {
const [selectedTheme, setSelectedTheme] = useState('system');
const handleMenuSelect = (event: {
nativeEvent: { identifier: string; title: string };
}) => {
setSelectedTheme(event.nativeEvent.identifier);
console.log('Selected:', event.nativeEvent.title);
};
return (
<View style={styles.container}>
<MenuView
checkedColor="#007AFF"
uncheckedColor="#8E8E93"
menuItems={[
{ identifier: 'light', title: 'Light Mode' },
{ identifier: 'dark', title: 'Dark Mode' },
{ identifier: 'system', title: 'System Default' },
]}
onMenuSelect={handleMenuSelect}
>
<View style={styles.menuButton}>
<Text style={styles.menuButtonText}>
🌓 Theme: {selectedTheme}
</Text>
</View>
</MenuView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
menuButton: {
backgroundColor: '#fff',
paddingHorizontal: 20,
paddingVertical: 12,
borderRadius: 8,
borderWidth: 1,
borderColor: '#ddd',
},
menuButtonText: {
fontSize: 16,
color: '#333',
},
});
export default App;
Use the selectedIdentifier
prop to fully control which item is marked as selected. Update it in your onMenuSelect
handler to keep iOS and Android behavior consistent.
const [selectedSort, setSelectedSort] = useState('date');
<MenuView
selectedIdentifier={selectedSort}
menuItems={[
{ identifier: 'date', title: 'Date' },
{ identifier: 'name', title: 'Name' },
{ identifier: 'size', title: 'Size' },
]}
onMenuSelect={({ nativeEvent }) => setSelectedSort(nativeEvent.identifier)}
>
<View style={styles.menuButton}>
<Text>📊 Sort by: {selectedSort}</Text>
</View>
</MenuView>
<MenuView
menuItems={[
{ identifier: 'profile', title: 'View Profile' },
{ identifier: 'settings', title: 'Settings' },
{ identifier: 'logout', title: 'Logout' },
]}
onMenuSelect={handleMenuSelect}
>
<View style={styles.customButton}>
<Text style={styles.customButtonText}>👤 Account Menu</Text>
</View>
</MenuView>
<MenuView
checkedColor="#5856D6"
menuItems={[
{ identifier: 'opt1', title: 'Option 1' },
{ identifier: 'opt2', title: 'Option 2' },
// ... many more items
{ identifier: 'opt20', title: 'Option 20' },
]}
onMenuSelect={handleMenuSelect}
>
<View style={styles.menuButton}>
<Text>đź“‹ Select Option</Text>
</View>
</MenuView>
Prop | Type | Required | Default | Description |
---|---|---|---|---|
children |
ReactNode |
Yes | - | The trigger component that opens the menu when tapped |
menuItems |
MenuItem[] |
Yes | [] |
Array of menu items to display |
onMenuSelect |
(event: MenuSelectEvent) => void |
No | - | Callback fired when a menu item is selected |
selectedIdentifier |
string |
No | - | Controlled selected item identifier; shows a native checkmark for the matching item |
checkedColor |
string |
No | #007AFF |
Color for checked/selected menu items (Android only) |
uncheckedColor |
string |
No | #8E8E93 |
Color for unchecked/unselected menu items (Android only) |
color |
string |
No | - | Reserved for future use |
style |
ViewStyle |
No | - | Style applied to the container view |
interface MenuItem {
identifier: string; // Unique identifier for the menu item
title: string; // Display text for the menu item
}
interface MenuSelectEvent {
identifier: string; // The identifier of the selected menu item
title: string; // The title of the selected menu item
}
The MenuView component accepts any React Native component as a child, which becomes the trigger for opening the menu. When tapped, a native context menu appears with the specified menu items.
File: android/src/main/java/com/menu/MenuView.kt
- Container:
FrameLayout
that accepts child views from React Native - Touch Handling: Intercepts all touch events at the parent level using
onInterceptTouchEvent()
- UI: Modal dialog with white background, rounded corners, and bottom positioning
- Menu Items: Implemented as
RadioButton
elements with custom styling - Colors: Full support for
checkedColor
anduncheckedColor
customization - Dividers: 0.5px light gray dividers between menu items
- Scrolling: Automatic scrolling for long lists (max 90% of screen height)
- Selection: Visual feedback with radio button selection states
Key Implementation Details:
- Uses
ViewGroupManager
to support child views - Touch events are intercepted to ensure child views don't block menu opening
- Modal appears at bottom with horizontal margins for mobile-friendly UX
File: ios/MenuView.mm
- Container:
RCTViewComponentView
(Fabric architecture) - Child Mounting: Uses
mountChildComponentView
andunmountChildComponentView
for Fabric compatibility - UI: Native
UIMenu
attached to an invisibleUIButton
overlay - Menu Items: Native
UIAction
elements with system styling - Trigger: Creates transparent button overlay on top of child view to show native menu
- Checkmarks: Controlled via
selectedIdentifier
and rendered usingUIMenuElementStateOn
- Selection: Emits
onMenuSelect
without mutating native state; updateselectedIdentifier
in React to reflect changes
Key Implementation Details:
- Disables user interaction on child views recursively
- Creates invisible button overlay that shows
UIMenu
on tap - Fully native iOS 14+ context menu appearance
- Supports both button and non-button child components
Feature | iOS | Android |
---|---|---|
Menu Style | Native UIMenu popover | Modal dialog at bottom |
Checkmark Color | System default (not customizable) | Fully customizable |
Unchecked Color | System default | Fully customizable |
Animation | Native iOS animation | Slide up animation |
Scrolling | Native UIMenu scrolling | Custom ScrollView (max 40% screen) |
Selection State | Controlled via selectedIdentifier |
use selectedIdentifier for cross-platform parity |
Appearance | iOS system theme | White background with rounded corners |
The repository includes a complete example project with 6 different use cases:
- Theme Selector - Shows state management with current selection
- Sort Options - Demonstrates sorting menu with dynamic labels
- File Actions - Common file operations menu
- Priority Selector - Priority level selection
- Custom Trigger - Fully custom styled button
- Long List - Scrollable menu with many items
cd example
yarn install
# For iOS
cd ios && pod install && cd ..
yarn ios
# For Android
yarn android
- React Native >= 0.68.0
- iOS >= 14.0 (for UIMenu support)
- Android API >= 21
iOS: Make sure you're running iOS 14 or later, as UIMenu is only available from iOS 14+.
Android: Ensure your child component doesn't have onPress
or other touch handlers that might interfere. The MenuView intercepts all touch events at the parent level.
Pass and update selectedIdentifier
. iOS does not shift the checkmark automatically—reflect selection in props via your onMenuSelect
handler.
The MenuView component requires a child component to act as the trigger. Always wrap your trigger in the MenuView:
// âś… Correct
<MenuView menuItems={items}>
<View><Text>Open Menu</Text></View>
</MenuView>
// ❌ Wrong - no children
<MenuView menuItems={items} />
MIT
Made with create-react-native-library