# @react-native-menu/menu, native context menus for React Native on Android and iOS

> This React Native component renders the platform's own menu: the Android PopupMenu and the iOS 14 and later UIMenu, with an ActionSheet fallback for older iOS. Actions carry a title, an optional subtitle, an icon name, colours, a state and nested subactions, and the component can be anchored to a view or triggered imperatively through a ref.

**react-native-menu/menu** — UIMenu Component for React Native

- Repository: https://github.com/react-native-menu/menu
- Stars: 1,238 · Forks: 92
- Language: Kotlin
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/react-native-menu-menu

## The platform's own menu, not a drawn imitation

The component's job is to put the operating system's menu on the screen rather than to draw something that looks like one. It provides the Android PopupMenu and the iOS 14 and later UIMenu components for React Native, and it falls back to ActionSheet for versions below iOS 14. That fallback is the first thing to understand, because it is the seam in the design. An ActionSheet is not a menu, it is a sheet of choices that slides up, and the documentation's own screenshot row shows all three: Android, iOS 14 and later, and iOS 13. If your app supports iOS 13, your users on that version get a different interaction model, and no amount of JavaScript will make the two identical. The primary language recorded for the repository is Kotlin, which reflects the Android native side, and the licence is MIT.

```sh
npm install @react-native-menu/menu
```

## The action model, and how much of it each platform honours

An action is an object, and the MenuAction type documents which properties exist and which platform each belongs to. The id is the identifier returned when the menu is selected, and the title is the only required field. The titleColor is Android only, and the subtitle, an elaborated title that explains the purpose of the action, is iOS 14 only. The state, describing the state of the action, is also iOS 14 only, and the attributes indicate the style, which is where the destructive flag lives. The image is supported on Android and iOS 13 and later, and it takes a name that resolves against project or system drawable resources on Android and against SF Symbols on iOS, with the type comment giving the two example forms directly. The imageColor follows the same platform rule. Any single action therefore carries a mix of fields that only some platforms will honour, and the honest way to build a menu is to decide per action which platform it targets.

```ts
export type MenuAction = {
  id?: string;
  title: string;
  titleColor?: number | ColorValue;
  subtitle?: string;
  attributes?: MenuAttributes;
  state?: MenuState;
  image?: string;
  imageColor?: number | ColorValue;
};
```

## Nested subactions, and the Android limit on them

The subactions field holds the actions to be displayed in the sub menu, and the type comment states the platforms involved: Android and iOS 14 and later. It also states the limitation in the same breath, that on Android it does not support nesting next sub menus in sub menu item. That is a native constraint rather than a bug in the binding, since the Android PopupMenu has a single level of submenu. The example shows what a nested action looks like in practice, with its own id, title, a titleColor with alpha, a subtitle, an icon selected per platform through Platform.select, a state, and a nested subaction of its own that carries the destructive attribute and no colour. The example also shows the shape of a non-nested destructive action, which sets the attributes and an icon but no colours. The state values shown in the example are strings such as mixed and on, which is a reminder that this is a small finite vocabulary rather than a free-form flag.

```jsx
subactions: [
  {
    id: 'nested1',
    title: 'Nested action',
    subtitle: 'State is mixed',
    state: 'mixed',
  },
  {
    id: 'nestedDestructive',
    title: 'Destructive Action',
    attributes: {
      destructive: true,
    },
  },
],
```

## The component props, and the ones marked platform only

The reference section lists the props with their types and whether they are required, and several are explicitly platform-scoped. The ref, which is a ref to the menu component, is Android only and optional. The title, the title of the menu, is iOS only and required. The isAnchoredToRight is an Android only boolean deciding whether the menu should be anchored to the right or left corner of the parent view, and the shouldOpenOnLongPress boolean decides whether the menu opens after a long press or on a normal press. The actions array of MenuAction objects is required. The themeVariant, a string to override the theme of the menu, is iOS only and takes light or dark, and the documentation points at a separate theme control package for overriding the theme universally across an app. Reading the props table is the fastest way to see the shape of the abstraction, and the answer is that it is thin: most of the props are Android only or iOS only, and only the actions array and the long press behaviour are shared.

```
`ref` (Android only)
`title` (iOS only)
`isAnchoredToRight` (Android only)
`shouldOpenOnLongPress`
`actions`
`themeVariant` (iOS only)
```

## Imperative use, and the declarative alternative

There are two ways to drive the menu, and the documentation is clear that the second is the nicer one. The imperative path is the ref, and it is Android only. You create it with useRef of a MenuComponentRef type, attach it to the MenuView, and call show on it from a button handler, and the button in the example is labelled as being for Android only. The declarative path is described as obtaining the action in a more React-ish fashion, and it points at the react-to-imperative package, with a worked example in the react-navigation-header-buttons repository. The distinction is the usual one between a component that owns its trigger and one where the trigger is just another view. Here the MenuView wraps its children, so the trigger can be any view you like, and the ref is the escape hatch for when you need to open it from somewhere that is not a child.

```jsx
const menuRef = useRef<MenuComponentRef>(null);

<MenuView
  ref={menuRef}
  title="Menu Title"
  onPressAction={({ nativeEvent }) => {
    console.warn(JSON.stringify(nativeEvent));
  }}
  shouldOpenOnLongPress={false}
>
  <View style={styles.button}>
    <Text style={styles.buttonText}>Test</Text>
  </View>
</MenuView>
```

## Installation and the one manual step

Installation is through npm or yarn, and the linking story is mostly handled for you. The package is automatically linked when building the app, as the React Native community CLI documentation describes, and all you need to do is run npx pod-install, which is the step that installs the iOS pods for the native code. So the setup is: install the package, run the pod install, and the native module is wired up. The build configuration in the package metadata shows a library rather than an application, with separate main, module, types and react-native entries pointing at the compiled output and the source, and the published files list includes the src, lib, android, ios and cpp directories along with the podspec, with the example and test directories excluded from the package. The example directory at the root is a full application with its own android and ios projects, and it includes a visionos directory, which is a detail worth noticing because it means the example has been set up for the third platform as well.

```sh
npx pod-install
```

## The repository, and how the project is put together

The tree is a React Native library with the native sides checked in: android/ and ios/ directories sit alongside src/ for the JavaScript, the plugin/ directory, a react-native-menu.podspec, a react-native.config.js, a babel.config.js and a metro.config.js. The example is a separate application directory with its own example/src and its own native projects. The tooling is worth a note because it is not the React Native default. Formatting and linting are handled by Biome, with a biome.json at the root and scripts named format and lint that call biome format --write and biome lint --write, and there is no eslint or prettier configuration in the tree. The build is done by bob, invoked from the prepare script, and the release script uses release-it with the conventional changelog plugin, and there is a script that regenerates the React Native test app Android manifest from the example app.json. The release history shows v2.0.0 from 2025-09-10 after v1.2.4 from 2025-07-31 and v1.2.3 from 2025-03-23, and the last push to the repository was on 2026-08-17.

```
src/
android/
ios/
plugin/
example/
```

## Conclusion

Adopt this component if your React Native app needs a menu that looks and behaves like the one on the device rather than a JavaScript imitation of it, and skip it if you need consistent behaviour across platforms, because the fallback and the native implementations do not behave identically. Verify three things before you ship: that your minimum iOS version is 14 or the ActionSheet fallback covers the difference in behaviour you care about; that your action definitions only use the properties the target platform supports, since titleColor is Android only, subtitle and state are iOS 14 only, and subactions have a documented nesting limit on Android; and that the icons resolve, since the image property expects a project or system drawable name on Android and an SF Symbol name on iOS.

## FAQ

### What does the @react-native-menu/menu component do?

It renders the Android PopupMenu and the iOS 14 and later UIMenu for React Native, and falls back to ActionSheet for versions below iOS 14. It takes a list of actions with titles, optional subtitles, icons, colours, states and nested subactions, and reports the selected action through onPressAction.

### Which menu action properties only work on one platform?

titleColor is Android only, subtitle and state are iOS 14 only, and image and imageColor work on Android and iOS 13 and later. The subactions field is documented as Android and iOS 14 and later, and on Android it does not support nesting next sub menus in sub menu item.

### How do I open the menu programmatically?

With a ref, which is Android only. You create a MenuComponentRef with useRef, pass it to the MenuView, and call show on it. The documentation also describes a declarative alternative based on the react-to-imperative package, with a worked example in the react-navigation-header-buttons repository.

### How do I install the React Native menu component?

With `npm install @react-native-menu/menu` or `yarn add @react-native-menu/menu`. The package is automatically linked when building the app, and the only manual step is running `npx pod-install` to install the iOS pods.

## Sources

- [Issues](https://github.com/react-native-menu/menu/issues)
- [License: MIT](https://github.com/react-native-menu/menu/blob/master/LICENSE)
- [react-native-menu/menu on GitHub](https://github.com/react-native-menu/menu)
- [README](https://github.com/react-native-menu/menu/blob/master/README.md)
- [Releases](https://github.com/react-native-menu/menu/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/react-native-menu-menu
