iOS14+ UIMenu components for react-native. Falls back to ActionSheet for versions below iOS14.
| iOS 14+ | iOS 13 |
|---|---|
![]() |
![]() |
via npm:
npm install @exodus/react-native-menuvia yarn:
yarn add @exodus/react-native-menuThere is an issue(facebook/react-native#29246) causing projects with this module to fail on build on React Native 0.63 and above.
This issue may be fixed in future versions of react native.
As a work around, look for lines in [YourPrject].xcodeproj under LIBRARY_SEARCH_PATHS with "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"", and change swift-5.0 to swift-5.3.
The package is automatically linked when building the app. All you need to do is:
npx pod-installimport { MenuView, MenuComponentRef } from "@exodus/react-native-menu";
const App = () => {
const menuRef = useRef < MenuComponentRef > null;
return (
<View style={styles.container}>
<MenuView
ref={menuRef}
title="Menu Title"
onPressAction={({ nativeEvent }) => {
console.warn(JSON.stringify(nativeEvent));
}}
actions={[
{
id: "add",
title: "Add",
titleColor: "#2367A2",
image: "plus",
imageColor: "#2367A2",
subactions: [
{
id: "nested1",
title: "Nested action",
titleColor: "rgba(250,180,100,0.5)",
subtitle: "State is mixed",
image: "heart.fill",
imageColor: "rgba(100,200,250,0.3)",
state: "mixed",
},
{
id: "nestedDestructive",
title: "Destructive Action",
attributes: {
destructive: true,
},
image: "trash",
},
],
},
{
id: "share",
title: "Share Action",
titleColor: "#46F289",
subtitle: "Share action on SNS",
image: "square.and.arrow.up",
imageColor: "#46F289",
state: "on",
},
{
id: "destructive",
title: "Destructive Action",
attributes: {
destructive: true,
},
image: "trash",
},
]}
shouldOpenOnLongPress={false}
>
<View style={styles.button}>
<Text style={styles.buttonText}>Test</Text>
</View>
</MenuView>
</View>
);
};It's also possible to obtain the action is a more React-ish, declarative fashion. Refer to the react-to-imperative package, and see an example here.
The title of the menu.
| Type | Required |
|---|---|
| string | Yes |
Boolean determining if menu should open after long press or on normal press
| Type | Required |
|---|---|
| boolean | No |
Actions to be displayed in the menu.
| Type | Required |
|---|---|
| MenuAction[] | Yes |
String to override theme of the menu. If you want to control theme universally across your app, see this package.
| Type | Required |
|---|---|
| enum('light', 'dark') | No |
Object representing Menu Action.
export type MenuAction = {
/**
* Identifier of the menu action.
* The value set in this id will be returned when menu is selected.
*/
id?: string;
/**
* The action's title.
*/
title: string;
/**
* (iOS14+ only)
* An elaborated title that explains the purpose of the action.
* @platform iOS
*/
subtitle?: string;
/**
* The attributes indicating the style of the action.
*/
attributes?: MenuAttributes;
/**
* (iOS14+ only)
* The state of the action.
* @platform iOS
*/
state?: MenuState;
/**
* (iOS13+ only)
* - The action's image.
* - Allows icon name included in SF Symbol
* @example
* image="plus"
*/
image?: string;
/**
* (iOS13+ only)
* - The action's image color.
*/
imageColor?: number | ColorValue;
/**
* (iOS14+ only)
* - Actions to be displayed in the sub menu
*/
subactions?: MenuAction[];
};The attributes indicating the style of the action.
type MenuAttributes = {
/**
* An attribute indicating the destructive style.
*/
destructive?: boolean;
/**
* An attribute indicating the disabled style.
*/
disabled?: boolean;
/**
* An attribute indicating the hidden style.
*/
hidden?: boolean;
};The state of the action.
/**
* The state of the action.
* - off: A constant indicating the menu element is in the "off" state.
* - on: A constant indicating the menu element is in the "on" state.
* - mixed: A constant indicating the menu element is in the "mixed" state.
*/
type MenuState = "off" | "on" | "mixed";Callback function that will be called when selecting a menu item. It will contain id of the given action.
| Type | Required |
|---|---|
| ({nativeEvent}) => void | No |
Callback function that will be called when the menu is dismissed. This event fires at the start of the dismissal, before any animations complete.
| Type | Required |
|---|---|
| () => void | No |
Callback function that will be called when the menu is opened. This event fires right before the menu is displayed.
| Type | Required |
|---|---|
| () => void | No |
Example usage:
<MenuView
onOpenMenu={() => {
console.log("Menu was opened");
}}
onCloseMenu={() => {
console.log("Menu was closed");
}}
>
<View>
<Text>Open Menu</Text>
</View>
</MenuView>In some cases, you might want to mock the package to test your components. You can do this by using the jest.mock function.
import type { MenuComponentProps } from "@exodus/react-native-menu";
jest.mock("@exodus/react-native-menu", () => ({
MenuView: jest.fn((props: MenuComponentProps) => {
const React = require("react");
class MockMenuView extends React.Component {
render() {
return React.createElement(
"View",
{ testID: props.testID },
props.actions.map((action) =>
React.createElement("Button", {
key: action.id,
title: action.title,
onPress: () => {
if (action.id && props?.onPressAction) {
props.onPressAction({ nativeEvent: { event: action.id } });
}
},
testID: action.id,
})
),
this.props.children
);
}
}
return React.createElement(MockMenuView, props);
}),
}));See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT

