Native context menus built with Nitro Modules. Supports actions, submenus, selection state, inline groups, palettes, tabbed menus, and tap-to-trigger.
npm install react-native-nitro-contextmenu react-native-nitro-modulesFor iOS, install pods:
cd ios && pod installAndroid requires no additional setup.
import { ContextMenu } from "react-native-nitro-contextmenu";
import type { MenuConfig } from "react-native-nitro-contextmenu";
function MyComponent() {
const menuConfig: MenuConfig = {
title: "",
items: [
{ actionKey: "copy", title: "Copy", image: { systemName: "doc.on.doc" } },
{ actionKey: "paste", title: "Paste", image: { systemName: "doc.on.clipboard" } },
{
actionKey: "delete",
title: "Delete",
image: { systemName: "trash" },
attributes: ["destructive"],
},
],
};
return (
<ContextMenu menuConfig={menuConfig} onPressAction={(key) => console.log("Selected:", key)}>
<View style={{ padding: 20 }}>
<Text>Tap me</Text>
</View>
</ContextMenu>
);
}| Prop | Type | Description |
|---|---|---|
menuConfig |
MenuConfig |
Required. The menu structure to display. |
trigger |
'tap' | 'longPress' |
How the menu is triggered. Default: 'tap'. |
onPressAction |
(actionKey: string) => void |
Called when a menu action is selected. |
onMenuWillShow |
() => void |
Called when the menu is about to appear. |
onMenuWillHide |
() => void |
Called when the menu is about to disappear. |
onPreviewPress |
() => void |
Called when the user taps the preview (iOS only). |
previewConfig |
PreviewConfig |
Customize the preview appearance (iOS only). |
children |
ReactNode |
Required. The trigger content. |
The root menu and any submenus share the same shape:
interface MenuConfig {
title?: string;
subtitle?: string; // iOS only
image?: SystemImage;
options?: MenuOption[];
preferredElementSize?: "small" | "medium" | "large"; // iOS 16+
tabs?: MenuTab[]; // iOS 16+ — when present, items is ignored
items: MenuElement[];
}Leaf actions that appear as tappable rows:
interface MenuAction {
actionKey: string; // Unique key returned in onPressAction
title: string;
subtitle?: string; // iOS 15+
image?: MenuImage; // SF Symbol or URL
selectedImage?: SystemImage; // Icon when state is 'on' (iOS 17+)
attributes?: MenuElementAttribute[];
state?: "on" | "off" | "mixed";
discoverabilityTitle?: string; // iOS only
}A union of MenuAction | MenuConfig. The native side discriminates by checking for actionKey (action) vs items (submenu).
// SF Symbol (iOS native, mapped to Android system drawables)
{
systemName: "star.fill";
}
// URL image (iOS only)
{
url: "https://example.com/icon.png";
}On Android, common SF Symbol names are automatically mapped to equivalent android.R.drawable icons. You can also provide your own drawables by adding resources named with dots replaced by underscores (e.g. doc_on_doc.xml for doc.on.doc).
Applied to MenuConfig.options:
| Option | iOS | Android |
|---|---|---|
'displayInline' |
Renders children inline with a separator. | Items added to parent menu with group separators. |
'destructive' |
Renders the submenu title in red. | No visual change. |
'singleSelection' |
Only one child can be state: 'on' at a time. |
Exclusive checkable group. |
'displayAsPalette' |
Renders children as a horizontal icon row. | No effect (renders as normal items). |
Applied to MenuAction.attributes:
| Attribute | iOS | Android |
|---|---|---|
'destructive' |
Red text and icon. | Icon tinted red. |
'disabled' |
Grayed out, not tappable. | Grayed out, not tappable. |
'hidden' |
Not visible in the menu. | Not visible in the menu. |
'keepsMenuPresented' |
Menu stays open after tapping (iOS 16+). | No effect. |
Nest MenuConfig objects inside items to create submenus:
const menuConfig: MenuConfig = {
title: "",
items: [
{
title: "Sort By",
image: { systemName: "arrow.up.arrow.down" },
items: [
{ actionKey: "sort-name", title: "Name" },
{ actionKey: "sort-date", title: "Date" },
{ actionKey: "sort-size", title: "Size" },
],
},
],
};Use singleSelection with state to create radio-style groups:
const [selected, setSelected] = useState('medium')
const menuConfig: MenuConfig = {
title: 'Text Size',
items: [
{
title: '',
options: ['displayInline', 'singleSelection'],
items: [
{ actionKey: 'small', title: 'Small', state: selected === 'small' ? 'on' : 'off' },
{ actionKey: 'medium', title: 'Medium', state: selected === 'medium' ? 'on' : 'off' },
{ actionKey: 'large', title: 'Large', state: selected === 'large' ? 'on' : 'off' },
],
},
],
}
<ContextMenu menuConfig={menuConfig} onPressAction={setSelected}>
{/* ... */}
</ContextMenu>Use displayInline to show items in the same level with visual separators, and preferredElementSize: 'small' for compact icon rows (iOS only):
const menuConfig: MenuConfig = {
title: "",
items: [
{
title: "",
options: ["displayInline"],
preferredElementSize: "small",
items: [
{ actionKey: "cut", title: "Cut", image: { systemName: "scissors" } },
{ actionKey: "copy", title: "Copy", image: { systemName: "doc.on.doc" } },
{ actionKey: "paste", title: "Paste", image: { systemName: "doc.on.clipboard" } },
],
},
{ actionKey: "select-all", title: "Select All", image: { systemName: "selection.pin.in.out" } },
],
};Use displayAsPalette for a horizontal icon strip (e.g. color pickers). On Android, items render as a normal list.
const menuConfig: MenuConfig = {
title: "Color",
items: [
{
title: "",
options: ["displayInline", "displayAsPalette"],
items: [
{ actionKey: "red", title: "Red", image: { systemName: "circle.fill" } },
{ actionKey: "green", title: "Green", image: { systemName: "circle.fill" } },
{ actionKey: "blue", title: "Blue", image: { systemName: "circle.fill" } },
],
},
],
};const menuConfig: MenuConfig = {
title: "",
items: [
{ actionKey: "edit", title: "Edit", image: { systemName: "pencil" } },
{ actionKey: "archive", title: "Archive", attributes: ["disabled"] },
{
title: "",
options: ["displayInline"],
items: [
{
actionKey: "delete",
title: "Delete",
image: { systemName: "trash" },
attributes: ["destructive"],
},
],
},
],
};Tabs display a row of buttons at the top of the menu. Tapping a tab swaps the items below without dismissing the menu. When tabs is provided, items is ignored.
const menuConfig: MenuConfig = {
title: "",
tabs: [
{
tabKey: "sort",
title: "Sort",
image: { systemName: "arrow.up.arrow.down" },
items: [
{ actionKey: "sort-name", title: "Name" },
{ actionKey: "sort-date", title: "Date" },
{ actionKey: "sort-size", title: "Size" },
],
},
{
tabKey: "filter",
title: "Filter",
image: { systemName: "line.3.horizontal.decrease" },
items: [
{ actionKey: "filter-images", title: "Images" },
{ actionKey: "filter-videos", title: "Videos" },
],
},
],
items: [],
};On iOS < 16 and on Android, the tab bar is omitted and the first tab's items are shown as a flat menu.
By default, the menu opens on a single tap. Use trigger="longPress" for long-press with preview (iOS):
<ContextMenu menuConfig={menuConfig} trigger="longPress">
<View>
<Text>Long press me</Text>
</View>
</ContextMenu>On iOS, no preview is shown when trigger is 'tap'.
Customize the context menu preview. This has no effect on Android.
<ContextMenu
menuConfig={menuConfig}
previewConfig={{
previewType: "view", // 'view' (default) or 'none'
preferredCommitStyle: "pop", // 'pop' (default) or 'dismiss'
backgroundColor: "#ffffff",
borderRadius: 16,
}}
onPreviewPress={() => console.log("Preview tapped")}
>
{/* ... */}
</ContextMenu>All types are exported for use in your own code:
import type {
ContextMenuProps,
MenuConfig,
MenuAction,
MenuElement,
MenuTab,
MenuOption,
MenuElementAttribute,
MenuElementSize,
MenuElementState,
MenuImage,
SystemImage,
UrlImage,
PreviewConfig,
} from "react-native-nitro-contextmenu";| Feature | iOS | Android |
|---|---|---|
| Actions with icons | SF Symbols + URL images | Mapped system drawables |
| Submenus | Nested menus | Nested menus |
| Inline groups | Separator + inline items | Group separators |
| Single selection | Checkmarks | Checkmarks |
| Destructive style | Red text + icon | Red icon tint |
| Disabled items | Grayed out | Grayed out |
| Hidden items | Excluded | Excluded |
| Trigger mode | trigger="tap" (default) or "longPress" |
trigger="tap" or "longPress" |
| Tabbed menus | Tab bar + swappable content (iOS 16+) | First tab shown as flat menu |
| Palette mode | Horizontal icon row (iOS 17+) | Normal menu items |
| Preview | Customizable preview with commit styles | Not supported |
| Subtitles | Below action title (iOS 15+) | Not supported |
keepsMenuPresented |
Menu stays open (iOS 16+) | Not supported |
selectedImage |
Alternate icon for checked state (iOS 17+) | Not supported |
| Feature | Minimum iOS |
|---|---|
| Context menus | 13.0 |
| Subtitles | 15.0 |
keepsMenuPresented, preferredElementSize, tabbed menus |
16.0 |
displayAsPalette, selectedImage |
17.0 |
Features are gracefully skipped on older iOS versions and unsupported Android features.
MIT

